Host-based URL routing and host-aware reversing for Django, with zero call-site changes.
django-hostmap routes requests to different URLconfs by host (subdomain or
full domain) and makes URL reversing host-aware without changing a single
call site: the stock reverse(), reverse_lazy() and {% url %} keep
working everywhere, including inside third-party apps. Links within the
current host stay path-relative, exactly as Django produces them; links to
views on another host come back as absolute URLs. Configuration is one
declarative host map in settings plus one middleware.
It is the ecosystem's replacement for django-hosts, whose parallel reverse
API forces invasive changes across templates and Python code and cannot fix
third-party apps that call django.urls.reverse().
pip install django-hostmapINSTALLED_APPS = [
# ...
"hostmap",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"hostmap.middleware.HostmapMiddleware", # before CommonMiddleware
"django.middleware.common.CommonMiddleware",
# ...
]ROOT_URLCONF remains required (Django needs it at startup) and should point
at the default entry's URLconf (hostmap.W003 warns on mismatch).
HOSTMAP = {
"www": {"subdomain": "www", "urlconf": "config.urls.www"},
"api": {"subdomain": "api", "urlconf": "config.urls.api"},
"apex": {"host": "example.com", "redirect_to": "www"},
}
HOSTMAP_PARENT_DOMAIN = "example.com"
HOSTMAP_DEFAULT = "www"With www active:
| Call | Returns |
|---|---|
reverse("blog:index") |
/blog/ (byte-identical to stock Django) |
reverse("api:user-detail", args=[7]) |
https://api.example.com/users/7/ |
{% url "api:user-detail" 7 %} |
the absolute URL, no template changes |
Same-host links stay relative; cross-host links come back absolute. Nothing at the call site changes.
| Setting | Default | Description |
|---|---|---|
HOSTMAP |
{} |
The host map. Empty map disables all behaviour |
HOSTMAP_PARENT_DOMAIN |
"" |
Domain joined to subdomain entries |
HOSTMAP_DEFAULT |
"" |
Entry for unmatched hosts and out-of-request reversing |
HOSTMAP_PATCH_REVERSE |
True |
Make stock reverse() / {% url %} host-aware |
HOSTMAP_SCHEME |
"https" |
Scheme for cross-host absolute URLs |
HOSTMAP_PORT |
"" |
Port appended to all generated hosts |
HOSTMAP_UNMATCHED |
"default" |
"default" routes unmatched hosts to the default entry; "reject" returns 404 |
HOSTMAP_REDIRECT_PERMANENT |
True |
redirect_to entries use 301, else 302 |
For code that needs an absolute URL regardless of the active host (emails, Celery tasks, API payloads, webhooks):
from hostmap.urls import reverse, build_absolute_uri, use_host
build_absolute_uri("api:user-detail", args=[7]) # always absolute
with use_host("api"):
reverse("user-detail", args=[7]) # api active
with use_host(host="acme.example.com"): # a wildcard host
reverse("dashboard")Modern browsers resolve *.localhost to loopback (RFC 6761), so the whole
map works locally with no /etc/hosts edits:
HOSTMAP_PARENT_DOMAIN = "localhost"
HOSTMAP_SCHEME = "http"
HOSTMAP_PORT = "8000"
ALLOWED_HOSTS = [".localhost"]Host routing is only as good as the Host header that reaches Django. Behind a
reverse proxy, either the proxy passes Host through unchanged or the
deployment sets USE_X_FORWARDED_HOST = True with the proxy sending
X-Forwarded-Host. Misrouted hosts behind nginx are otherwise the first
support issue.
HOSTMAP_PATCH_REVERSE = False degrades cleanly to routing-only: the stock
reverse() is left untouched (cross-host links regress to paths), routing
keeps working, and the explicit API still works. Set it if a Django upgrade
lands before a compatible django-hostmap release; the startup self-test and
hostmap.W004 warn before anything breaks.
manage.py hostmap # print the resolved map for the current settingsMIT. See LICENSE.