URL Namespacing
Reviewed & published by Brayan K
URL namespacing in Django lets each app label its URL names with a unique prefix so that two apps can both have a URL called 'detail' without colliding — you set app_name in the app's urls.py and then refer to URLs as 'appname:urlname' when reversing them.
Part of the free Django course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
In this lesson you'll name your URL patterns, reverse them by name instead of hard-coding paths, then add an application namespace so several apps can coexist without their URL names clashing.
What You'll Learn in This Lesson
- Why hard-coded URLs are fragile and named URLs are not
- Giving patterns a name= and reversing them with reverse()
- Using the url template tag instead of literal paths
- Setting app_name to create an application namespace
- Including an app's urls with include('app.urls')
- Reversing namespaced URLs that take arguments
1 Naming URLs and Reversing Them
Every URL pattern can be given a name with the name= argument of path(). Once a pattern has a name, you never have to write its literal path again — you ask Django to build the path for you with reverse() in Python or the {% url %} tag in templates.
This matters because paths change. If you hard-code /posts/5/ in fifty places and later move it to /articles/5/, you have fifty edits. With a named URL you change the pattern once and every reverse() call follows along.
# blog/urls.py — give each pattern a name
from django.urls import path
from . import views
urlpatterns = [
path("", views.index, name="index"),
path("posts/<int:pk>/", views.post_detail, name="post_detail"),
]
# In a view you build URLs by name, never by hard-coding the path:
from django.urls import reverse
url = reverse("post_detail", args=[5]) # -> "/posts/5/"
# In a template you use the url tag instead of a literal href:
# <a href="{% url 'post_detail' post.pk %}">Read more</a># reverse() turns a NAME into a PATH. Here's a tiny simulation of what
# Django does internally: it stores patterns keyed by name and rebuilds
# the path on demand. No magic.
named_patterns = {
"index": "/",
"post_detail": "/posts/{pk}/",
}
def reverse(name, args=None):
template = named_patterns[name] # look up by name
if args:
return template.format(pk=args[0]) # fill in the argument
return template
print(reverse("index"))
print(reverse("post_detail", args=[5]))
# ✅ Expected output:
# /
# /posts/5/2 Adding an Application Namespace
Names solve repetition, but they create a new problem: what if a blog app and a shop app both have a pattern named detail? Django would not know which one you mean. The fix is an application namespace: set app_name at the top of the app's urls.py.
After that, every name in that app is addressed as 'appname:name'. You wire the app into the project with include('app.urls'), and Django picks up the app_name automatically.
# blog/urls.py — declare the application namespace
from django.urls import path
from . import views
app_name = "blog" # <- this is the namespace prefix
urlpatterns = [
path("", views.index, name="index"),
path("<int:pk>/", views.detail, name="detail"),
]
# project/urls.py — include the app
from django.urls import path, include
urlpatterns = [
path("blog/", include("blog.urls")),
path("shop/", include("shop.urls")),
]
# Now names never collide. Reverse them WITH the prefix:
reverse("blog:detail", args=[5]) # -> "/blog/5/"
reverse("shop:detail", args=[5]) # -> "/shop/5/"# Two apps, both with a "detail" pattern. Without a namespace the second
# would overwrite the first. The "app_name:name" key keeps them separate.
patterns = {
"blog:detail": "/blog/{pk}/",
"shop:detail": "/shop/{pk}/",
}
def reverse(qualified_name, args=None):
template = patterns[qualified_name] # key includes the namespace
return template.format(pk=args[0]) if args else template
print(reverse("blog:detail", args=[5]))
print(reverse("shop:detail", args=[5]))
# Both apps can have a name called "detail" with no collision.
# ✅ Expected output:
# /blog/5/
# /shop/5/3 Reversing Namespaced URLs With Arguments
Most useful URLs capture a value — a primary key, a slug, a page number. You pass these to reverse() through args (positional) or kwargs (by name), and to the {% url %} tag as space-separated values after the name.
# In a view: positional or keyword arguments both work
from django.urls import reverse
reverse("blog:post_detail", args=[42]) # -> "/blog/42/"
reverse("blog:post_detail", kwargs={"pk": 42}) # -> "/blog/42/"
# In a template: pass the value right after the namespaced name
# <a href="{% url 'blog:post_detail' post.pk %}">Read post</a>
# <a href="{% url 'blog:post_detail' pk=post.pk %}">Read post</a>
# A common pattern: redirect to the just-created object's detail page
from django.shortcuts import redirect
def create_post(request):
# ... save the post ...
new_pk = 42
return redirect("blog:post_detail", pk=new_pk)# reverse() with arguments, namespaced. The number of args must match the
# number of captured parameters in the pattern, or Django raises
# NoReverseMatch.
patterns = {
"blog:post_detail": "/blog/{pk}/",
}
def reverse(name, args=None):
template = patterns[name]
needed = template.count("{") # how many params the pattern wants
args = args or []
if len(args) != needed:
raise ValueError(
"NoReverseMatch: expected "
+ str(needed) + " arg(s), got " + str(len(args))
)
return template.format(pk=args[0])
print(reverse("blog:post_detail", args=[42]))
try:
reverse("blog:post_detail") # missing the pk -> error
except ValueError as e:
print(e)
# ✅ Expected output:
# /blog/42/
# NoReverseMatch: expected 1 arg(s), got 0🎯 Your Turn: Name the URL
Fill in the blank so the lookup uses the correct namespaced name 'blog:detail' for the blog app's detail page.
# 🎯 YOUR TURN: replace ___ with the namespaced name "blog:detail"
patterns = {
"blog:detail": "/blog/{pk}/",
"shop:detail": "/shop/{pk}/",
}
def reverse(name, pk):
return patterns[name].format(pk=pk)
name = "___"
print(reverse(name, 7))
# EXPECTED OUTPUT:
# /blog/7/Common Errors (And How to Fix Them)
❌ NoReverseMatch: Reverse for 'detial' not found
The name is misspelled, or you passed the wrong number of arguments for the pattern.
✅ Fix: check the exact spelling of name= and make sure your args match the captured parameters.
❌ NoReverseMatch after adding app_name
You set app_name = "blog" but still reverse the bare name 'detail', so Django cannot find it.
✅ Fix: add the prefix — reverse 'blog:detail' instead of 'detail'.
❌ NoReverseMatch: keyword argument 'pk' not found
The number or names of the arguments you passed do not match the pattern's captured groups.
✅ Fix: pass exactly the values the pattern captures — for <int:pk> pass one value via args=[pk] or kwargs={"pk": pk}.
📋 Quick Reference
| Tool | What It Does |
|---|---|
| name="detail" | Labels a URL pattern so it can be reversed by name |
| reverse("blog:detail", args=[pk]) | Builds a path from a namespaced name in Python |
| {% url "blog:detail" pk %} | Builds a path from a namespaced name in a template |
| app_name = "blog" | Declares the application namespace in the app's urls.py |
| "blog:detail" | The namespace (before the colon) plus the URL name |
🏆 Mini Challenge: A Tiny URL Resolver
Build a mini resolver that registers named patterns for two apps and reverses them with arguments — exactly what Django does for you behind the scenes.
# 🏆 Mini Challenge: a miniature namespaced URL resolver.
class Resolver:
def __init__(self):
self.patterns = {}
def register(self, app_name, name, template):
# Store under the "app_name:name" key, just like Django.
self.patterns[app_name + ":" + name] = template
def reverse(self, qualified_name, **kwargs):
template = self.patterns[qualified_name]
return template.format(**kwargs)
resolver = Resolver()
resolver.register("blog", "post_detail", "/blog/{pk}/")
resolver.register("shop", "product", "/shop/{slug}/")
print(resolver.reverse("blog:post_detail", pk=42))
print(resolver.reverse("shop:product", slug="hat"))
# EXPECTED OUTPUT:
# /blog/42/
# /shop/hat/❓ Frequently Asked Questions
Lesson complete — your URLs are namespaced and collision-proof!
You now know how to name URL patterns, reverse them with reverse() and the url tag, declare an app_name so apps never clash, and pass arguments to namespaced URLs without tripping NoReverseMatch.
Practice quiz
What problem does URL namespacing solve?
- It speeds up URL matching
- It compresses long URLs
- It hides URLs from search engines
- It lets two apps share a URL name like 'detail' without colliding
Answer: It lets two apps share a URL name like 'detail' without colliding. Namespacing prefixes names with the app, so identical names in different apps do not clash.
How do you give a URL pattern a name in path()?
- Pass it as the first positional argument
- Pass the name= argument, e.g. path('', views.index, name='index')
- Set it in settings.py
- Add a @name decorator to the view
Answer: Pass the name= argument, e.g. path('', views.index, name='index'). The name= argument of path() labels a pattern so it can be reversed by name.
Which function builds a URL path from a name in Python?
- reverse('post_detail', args=[5])
- resolve('post_detail')
- build_url('post_detail')
- path('post_detail')
Answer: reverse('post_detail', args=[5]). reverse() turns a (namespaced) name plus args into the matching URL path.
How do you set an application namespace?
- With namespace= on every path()
- In the project's settings.py
- By setting app_name in the app's urls.py
- By naming the app folder with a colon
Answer: By setting app_name in the app's urls.py. Setting app_name in the app's urls.py declares the application namespace.
After setting app_name = 'blog', how do you reverse the 'detail' pattern?
- reverse('detail')
- reverse('blog:detail')
- reverse('detail:blog')
- reverse('blog.detail')
Answer: reverse('blog:detail'). Once app_name is set, the name must be prefixed: reverse('blog:detail').
Which template tag builds a URL from a namespaced name?
- {{ url 'blog:detail' }}
- {% reverse 'blog:detail' %}
- {% link 'blog:detail' %}
- {% url 'blog:detail' post.pk %}
Answer: {% url 'blog:detail' post.pk %}. The {% url 'blog:detail' post.pk %} tag builds the path from a namespaced name in templates.
How do you wire an app's URLs into the project urls.py?
- path('blog/', include('blog.urls'))
- path('blog/', 'blog.urls')
- include(path('blog/'))
- path('blog/', app_name='blog')
Answer: path('blog/', include('blog.urls')). You mount an app's URLs with include('blog.urls'); Django picks up its app_name automatically.
What does NoReverseMatch usually mean?
- The view returned None
- The template was not found
- Django could not match the name and arguments you passed to reverse()
- The database is unavailable
Answer: Django could not match the name and arguments you passed to reverse(). NoReverseMatch means no pattern matched the name/args — often a typo or missing namespace prefix.
How do you pass a captured value to reverse() for a pattern like <int:pk>?
- reverse('blog:detail', 5)
- reverse('blog:detail', args=[5])
- reverse('blog:detail').format(5)
- reverse('blog:detail', pk)
Answer: reverse('blog:detail', args=[5]). Pass positional values via args=[5] (or kwargs={'pk': 5}) to fill the captured parameter.
What does the namespace argument of include() provide, versus app_name?
- An instance namespace, letting the same app be mounted multiple times under different prefixes
- A faster URL resolver
- A way to disable a URL pattern
- The same thing as name= on a path
Answer: An instance namespace, letting the same app be mounted multiple times under different prefixes. app_name is the application namespace; include()'s namespace= sets an instance namespace for mounting an app more than once.
Continue this course
- Previous: Template Inheritance
- Next: The Messages Framework — Show one-time flash notifications to users after an action
- Quick reference: Django cheat sheet › Views & URLs
Frequently asked questions
What is URL namespacing in Django?
URL namespacing lets each app label its URL names with a unique prefix so that two apps can both have a URL called 'detail' without colliding. You set app_name in the app's urls.py and then refer to URLs as 'appname:urlname' when reversing them with reverse() or the url template tag. Without it, a name like 'detail' would be ambiguous once more than one app defines it.
What is the difference between app_name and the namespace argument of include()?
Setting app_name in an app's urls.py defines an application namespace — a fixed label that travels with the app wherever it is included. The namespace argument of include() defines an instance namespace, letting you mount the same app more than once under different prefixes. For most projects you only need app_name; the instance namespace matters when you include one app multiple times.
Why am I getting a NoReverseMatch error?
NoReverseMatch means Django could not find a URL pattern that matches the name and arguments you passed to reverse() or the url tag. Common causes are misspelling the name, forgetting the 'appname:' prefix after you added app_name, or passing the wrong number of arguments. Check that the name exists, that the namespace prefix is correct, and that args match the captured parameters in the pattern.
Should I always use namespaced URL names?
Yes, for any project with more than one app it is best practice. Namespacing keeps your URL names unambiguous and makes apps reusable, because a reusable app can reverse its own URLs as 'appname:name' without worrying about clashes in the projects that install it. It costs almost nothing to set app_name once and reference names with the prefix.