Django URL Dispatcher Basics
Master Django's URL dispatcher: map URLs to views, use path converters, and organize routes. Hands-on exercises included.
Focus: Django URL dispatcher
You’ve built models and views, but how does Django know which view to call when someone visits /about/? Left to chance, your app would return a 404 for every request. That’s exactly the pain the Django URL dispatcher solves — it’s the map that connects a URL to the right view. In this lesson, you’ll learn how to wire URLs to views step by step, from the simplest route to path converters that capture dynamic data. By the end, you’ll be able to create clear, maintainable URL configurations that make your Django app feel cohesive and intuitive.
The problem this lesson solves
Imagine you’ve just written a beautiful view function that renders a product listing. You fire up the dev server, type http://127.0.0.1:8000/products/, and… Page not found (404). No matter how perfect your view code is, Django can’t guess the URL. That’s the core problem: Django needs an explicit mapping between a URL pattern and a view function. Without it, every request falls through to the 404 handler.
The URL dispatcher isn’t just about avoiding 404s. It’s about designing clean, predictable URLs that users can remember and share. A messy URL like /index.php?page=3&sort=asc is hard to type and harder to understand. The dispatcher lets you turn that into /products/3/sort/asc/ — which reads naturally and gives search engines and users alike a clear signal about page content.
In a growing Django project, you’ll have dozens (or hundreds) of views. The URL dispatcher keeps them organized. Instead of one giant urlpatterns list, you can split routes across apps — a key practice for maintainability. This lesson gives you the mental framework and the hands-on skills to wire URLs like a pro.
Core concept / mental model
Think of the URL dispatcher as a switchboard operator in an old telephone exchange. When a call comes in (an HTTP request), the operator looks at the number (the URL path) and connects it to the correct department (the view). If no match exists, the operator politely says “sorry, wrong number” — a 404.
Django’s DISPATCH operates in two layers:
- Project-level
urls.py: the main switchboard. It typically includes routes from your installed apps. - App-level
urls.py: each app has its own switchboard, and the project ‘includes’ it. This keeps each app self-contained.
A URL pattern is simply a string combined with a view function (and optionally a name). Django goes through the list in order and uses the first match. That order matters — put specific patterns before generic ones.
Here’s the anatomy of a path():
from django.urls import path
from . import views
urlpatterns = [
path('products/', views.product_list, name='product-list'),
]
'products/'— the URL pattern (without the domain). Django adds a leading slash automatically and expects no trailing slash unless you add it.views.product_list— the view function to call.name='product-list'— a unique identifier you can use in templates and Python code (e.g.,{% url 'product-list' %}). Not required, but strongly recommended.
The dispatcher strips the domain and query string, then matches the path against your patterns. It ignores the HTTP method (GET, POST) — that’s the view’s job. This single responsibility is why Django stays so simple.
💡 Pro tip: Always give your routes a
name. It lets you change the URL later without touching templates or redirects.
How it works step by step
Let’s trace the journey of a request through the dispatcher:
- A user enters a URL in the browser, like
http://myapp.com/blog/archive/2024/. - Django strips the domain and the leading slash, leaving
/blog/archive/2024/. - The URLconf module (usually
project/urls.py) is loaded. Django checks itsurlpatternslist from top to bottom. - Each pattern is tested against the remaining path. If a pattern includes an
include(), Django strips the matched prefix and passes the rest to the included app’surlpatterns. - When a match is found, Django calls the view with an
HttpRequestobject and any captured keyword arguments. - If no pattern matches, Django raises a
404exception, which triggers the built-in 404 handler.
This step-by-step flow is the same in every Django project. The magic lies in how you structure your patterns.
Path converters — capturing dynamic values
Most URLs aren’t static like /about/. You’ll want /products/42/ where 42 is a product ID. Django provides path converters to capture parts of the URL and pass them as arguments:
int— matches zero or more digits, converts to an integer.str— matches any non-empty string except/.slug— matches letters, numbers, hyphens, and underscores.uuid— matches a UUID format.path— matches any characters including/(use sparingly).
Here’s how you capture an integer:
path('products/<int:product_id>/', views.product_detail, name='product-detail')
Now, product_detail(request, product_id) receives product_id as an integer. Django takes care of type conversion — no manual parsing needed.
Hands-on walkthrough
Time to wire some URLs! We’ll build a tiny store app with a list view and a detail view. Follow along in your own project.
Step 1: Create the app and views
If you haven’t yet, create an app called store and add a couple of views in store/views.py:
# store/views.py
from django.http import HttpResponse
def product_list(request):
return HttpResponse("<h1>All products</h1>")
def product_detail(request, product_id):
return HttpResponse(f"<h1>Product {product_id}</h1>")
Step 2: Create the app’s urlconf
Create store/urls.py inside the app folder:
# store/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.product_list, name='product-list'),
path('<int:product_id>/', views.product_detail, name='product-detail'),
]
Step 3: Include app URLs in the project
In your project’s urls.py (the one with the settings import), add an include:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('products/', include('store.urls')),
]
Now the app routes live under /products/. The empty string in store/urls.py matches the rest of the path after /products/, so /products/ serves product_list and /products/42/ serves product_detail.
Step 4: Run and test
Start the server and visit these URLs:
python manage.py runserver
http://127.0.0.1:8000/products/→ “All products”http://127.0.0.1:8000/products/42/→ “Product 42”http://127.0.0.1:8000/products/abc/→ 404 (becauseabcisn’t an integer)
💡 Pro tip: Always start your URL patterns with the most specific ones. If you put
<int:product_id>/before'', Django would try to match''against the empty string, but since<int:...>requires at least one digit, it falls through — but with different converters, ordering can break things. Keep specific first.
Compare options / when to choose what
Django offers three main ways to define URL patterns:
| Method | When to use | Example |
|---|---|---|
path() |
Most views, with or without converters | path('about/', views.about) |
re_path() |
Complex patterns needing regular expressions | re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive) |
include() |
Splitting routes across apps | path('store/', include('store.urls')) |
Choose path() by default — it’s simpler and safer. Use re_path() only when you need pattern matching beyond what converters offer, like validating ranges. Use include() for any app that has more than a couple of routes; it keeps the project clean and each app self-contained.
Variations
- Using
re_path()with regex groups: You can pass captured groups as named kwargs — powerful but harder to read. - Direct view import: Instead of
include(), you can import the view directly into the projecturls.py(e.g.,path('products/', views.product_list)). Fine for tiny projects, but it breaks app modularity. - Third-party routers (e.g., DRF routers): For REST APIs, routers auto-generate URLs from view sets — a different paradigm entirely.
Troubleshooting & edge cases
Even in simple setups, things go wrong. Here are the most common errors:
- 404 on a pattern that should match — Check the trailing slash. If your pattern has no slash and you visit with one (or vice versa), Django redirects (if
APPEND_SLASH=True) or 404s. Always be consistent. - 400 Bad Request when using
path('<str:pk>')with a slug containing hyphens —strmatches robustly, but if your converter is too restrictive (likeint), missing a hyphen causes failure. Useslugif you expect hyphens. NoReverseMatchin templates — This happens when you use{% url 'name' %}but the name isn’t defined or the required kwargs are missing. Double-check yournameand convert parameters.- Ordering issues — If you have
path('products/new/', ...)beforepath('products/<int:id>/', ...), everything works. But if you swap them,/products/new/will try to matchnewas an integer and fail with a 404. Always place literal routes before dynamic ones. - Matching too broadly —
path('', include('store.urls'))puts all store routes at the root, which can conflict with admin. Use a prefix likestore/.
When debugging, look at the URLconf traceback in the Django error page — it shows which patterns were tried. That’s your best friend.
What you learned & what's next
You now understand the Django URL dispatcher end to end. You learned how to:
- Explain why explicit URL mapping is essential to avoid 404s and keep your code clear.
- Apply
path()with converters like<int:product_id>to capture dynamic data. - Organize routes across apps using
include(). - Debug common URL pattern mistakes like ordering and trailing slashes.
This foundation is critical because next we’ll dive into views and templates — you’ll replace those plain HttpResponse strings with real HTML. The URLs you’ve wired will connect to templates that render dynamic content. With the dispatcher under your belt, you’re ready to build pages that actually look like a website.
Head to the next lesson and start turning your views into full-fledged templates!
Practice recap
Now build a mini project: create an app called blog, add a view post_detail(request, post_id), and wire /posts/<int:post_id>/ in the project URLconf. Use a URL name post-detail and test both a valid ID and a non-integer to verify the 404 behavior. This hands-on exercise cements everything you learned today and preps you for views and templates next.
Common mistakes
- Forgetting the trailing slash — Django expects it by default; if you omit it, you may get a redirect or 404.
- Placing a dynamic pattern like
<int:product_id>/before a literal pattern likeproducts/new/, causing the literal to be captured as an ID and fail. - Not naming your URL patterns — later you can’t reverse them with
{% url %}orreverse(), making changes painful. - Using
re_path()for everything whenpath()with converters is simpler and less error-prone.
Variations
- Use
re_path()with named regex groups for complex patterns liker'^articles/(?P<year>[0-9]{4})/$'. - Import views directly into the project
urls.pyinstead of usinginclude()— works for tiny apps but reduces modularity. - Leverage DRF routers (e.g.,
DefaultRouter) which generate URL patterns automatically from view sets — ideal for REST APIs.
Real-world use cases
- E-commerce product pages:
/products/<int:product_id>/maps to a detail view fetching that product. - Blogging platform:
/posts/<slug:slug>/routes to an article view using a human-readable slug. - SaaS dashboard:
/dashboard/<uuid:account_id>/uses a UUID converter to prevent enumeration.
Key takeaways
- The URL dispatcher is Django's switchboard — it maps each request path to a view function, or returns a 404.
path()with converters like<int:...>and<slug:...>is the simplest way to capture dynamic parts of a URL.- Organize routes with
include()to keep each app's URLs self-contained and maintainable. - URL pattern order matters: always put literal paths before dynamic ones.
- Always name your URL patterns — it enables clean linking in templates and code with
{% url %}andreverse(). - If a URL doesn't match, Django tries every pattern in order; use the error page's URLconf traceback to debug.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.