URL Dispatcher
A clean and elegant URL scheme is vital for a high-quality web application. Django allows you to design URLs exactly how you want them without framework limitations.
To design URLs for an app, you create a Python module informally called a URLconf (URL configuration). This module is pure Python code that maps URL path expressions to Python functions (your views).
How Django Processes a Request
When a user requests a page from your Django site, the system follows a specific algorithm to determine which Python code to execute.
- First, Django determines the root URLconf module to use. This is usually defined in the
ROOT_URLCONFsetting. - Next, Django loads that Python module and looks for a variable named
urlpatterns. This variable is a sequence ofpath()orre_path()instances. - Django then runs through each URL pattern in order and stops at the first one that matches the requested URL.
- Once a match is found, Django imports and calls the matching view (Django views are covered in subsequent pages of this chapter). The view receives an
HttpRequestinstance as its first argument and any captured values from the URL as remaining arguments. - If no URL pattern matches, or if an exception is raised, Django invokes an appropriate error-handling view.
Here is a sample URLconf setup:
from django.urls import path
from . import views
urlpatterns = [
path("articles/2003/", views.special_case_2003),
path("articles/<int:year>/", views.year_archive),
path("articles/<int:year>/<int:month>/", views.month_archive),
path("articles/<int:year>/<int:month>/<slug:slug>/", views.article_detail),
]In this example, the user might request /articles/2005/03/. Django would match this to the third entry in the list and call views.month_archive(request, year=2005, month=3).
Path Converters
As seen above, you can capture values from the URL using angular brackets. These captures can optionally include a converter type to specify the data format.
| Converter | Description |
|---|---|
| str | Matches any non-empty string, excluding the path separator /. This is the default if you do not include a converter. |
| int | Matches zero or any positive integer. Returns an int. |
| slug | Matches any slug string consisting of ASCII letters or numbers, plus the hyphen and underscore characters. |
| uuid | Matches a formatted UUID. To prevent multiple URLs from mapping to the same page, dashes must be included and letters must be lowercase. |
| path | Matches any non-empty string, including the path separator /. This allows you to match against a complete URL path rather than just a segment. |
Leading and Trailing Slashes
There is no need to add a leading slash to your patterns because every URL has that by default. For example, write articles instead of /articles.
URL patterns should normally end with a trailing slash. Django’s conventions, tools and middleware assume this style, and it allows Django to automatically redirect requests that are missing the trailing slash.
path("articles/", views.article_list)Here, by default, a request to /articles will be redirected to /articles/, and a request to /articles/ will match the pattern directly.
TIP
This redirection behavior is controlled by the APPEND_SLASH setting, which is enabled by default.
URL Pattern Ordering
URL patterns are evaluated top to bottom, and Django stops at the first match.
urlpatterns = [
path("articles/2003/", views.special_case_2003),
path("articles/<int:year>/", views.year_archive),
]This means:
- More specific patterns should appear before more general ones.
- Ordering mistakes can cause unexpected matches or unreachable URLs.
Regular Expression Paths with re_path()
While path() covers most use cases with a clean and readable syntax, Django also provides re_path() for situations where you need full control over URL matching using regular expressions. A few key points to note about re_path():
re_path()uses Python regular expressions.- Captured groups are passed to the view as keyword arguments.
- Named groups are recommended for clarity. The syntax for named regular expression groups is
(?P<name>pattern), wherenameis the name of the group andpatternis some pattern to match. - Unnamed (positional) capturing groups are passed to the view as positional arguments, in the order they appear in the regular expression.
- Non-capturing groups
(?:...)affect matching but do not produce arguments for the view. - Regexes are evaluated against the entire remaining URL path, not just a segment.
from django.urls import re_path
from . import views
urlpatterns = [
re_path(
# The regex is matched against the ENTIRE remaining URL path.
# Example matched URL (after any include() prefix is removed):
# articles/2025/page-3/
r"^articles/"
r"(?P<year>[0-9]{4})/" # Named capturing group → passed as keyword argument `year`
r"(?:page-)?" # Non-capturing group → used only for matching, NOT passed to the view
r"([0-9]+)/" # Positional capturing group → passed as positional argument
r"$",
views.article_page,
),
]This will pass the following arguments to views.article_page:
# views.py
def article_page(request, page, year):
...In modern Django projects, path() is preferred for readability, but re_path() remains useful for legacy patterns or complex matching rules.
Including Other URLconfs
As projects grow, keeping all URL patterns in a single file becomes unmanageable. Django solves this using the include() function, which allows you to reference URL patterns from other modules.
from django.urls import include, path
urlpatterns = [
path("blog/", include("blog.urls")),
path("shop/", include("shop.urls")),
]How this works:
- Django strips the matched prefix (
blog/) and passes the remaining path toblog.urls. - This encourages reusable and pluggable apps.
- Each app manages its own URL structure independently.
NOTE
An included URLconf receives any captured parameters from parent URLconfs.
You can also include patterns directly:
urlpatterns = [
path("articles/", include([
path("<int:year>/", views.year_archive),
path("<int:year>/<int:month>/", views.month_archive),
])),
]Passing Extra Options to Views
You can pass additional keyword arguments, as a Python dictionary, to views directly from the URLconf. These extra arguments are merged with captured URL parameters and passed to the view function.
urlpatterns = [
path("blog/<int:year>/", views.year_archive, {"foo": "bar"}),
]In this example, for a request to /blog/2005/, Django will call views.year_archive(request, year=2005, foo='bar').
Passing Extra Options to include()
You can also pass extra options to include() and each view in the included URLconf will be passed the extra options.
urlpatterns = [
# The extra options are passed to every view in blog/urls.py
path("blog/", include("blog.urls"), {"foo": "bar"}),
]Reverse URL Resolution
Hardcoding URLs in your code makes applications fragile. If a URL pattern changes, every place that refers to that URL must also be updated. Django solves this problem using reverse URL resolution.
Instead of referring to a URL by its path, you refer to it by a name. Django then figures out the correct URL automatically.
Naming URL Patterns
TIP
Always refer to URLs by name instead of hardcoding paths.
Naming your URLs is one of the most important best practices in Django. A named URL can be referenced throughout your project without hardcoding paths.
urlpatterns = [
path("articles/<int:year>/", views.year_archive, name="year-archive"),
]The name (year-archive) acts as a stable identifier for this URL.
Generating URLs in Python Code
Django provides the reverse() function to generate a URL from its name.
from django.urls import reverse
# url = /articles/2025/
url = reverse("year-archive", args=[2025])The values passed to args are substituted into the URL pattern in the order they appear. You can also pass values using keyword arguments.
url = reverse("year-archive", kwargs={"year": 2025})URL Namespaces
As your project grows, it’s common to have more than one app that uses the same URL names. To avoid name conflicts, Django lets you group URLs under a namespace.
A namespace acts like a label that keeps similarly-named URLs separate.
Imagine two apps, blog and shop, both define a URL named index:
# blog/urls.py
urlpatterns = [
path("", views.index, name="index"),
]
# shop/urls.py
urlpatterns = [
path("", views.index, name="index"),
]If you refer to index elsewhere in your project, Django wouldn’t know which one you mean unless you give them namespaces.
Add a Namespace
Namespaces are defined in two places:
- Inside the included app’s URLconf using
app_name.
app_name = "blog"
urlpatterns = [
path("", views.index, name="index"),
]- In the project's root URLconf where the app is included.
path("blog/", include("blog.urls")),Referring to Namespaced URLs
When you want to reverse a URL (using reverse()), you include the namespace and the URL name separated by a colon:
from django.urls import reverse
blog_index = reverse("blog:index")
shop_index = reverse("shop:index")Error Handling
When something goes wrong while processing a request, Django uses special error views to show error pages. By default, Django shows simple built-in pages like “404 Not Found” or “500 Server Error”. But you can replace these with your own functions to customize the response.
These are the most common HTTP errors Django can handle:
| Error | Meaning |
|---|---|
404 | Page not found (The requested URL did not match any URL pattern) |
500 | Server error (An unexpected error occurred while processing the request) |
403 | Permission denied (The user does not have permission to access the resource) |
400 | Bad request (The request sent by the client was malformed) |
Custom Error handlers
To override Django’s default behavior, you can use either custom templates or custom view functions.
Custom Templates
The easiest way to customize an error page is to create a template in your root template folder named after the error code. If you create a file named 404.html or 500.html in that directory, Django will automatically find and use it.
Custom View Functions
If you need more control, such as logging specific data or passing extra context to the page, you can write custom view functions instead. You assign these functions to special variables in the project’s root URLconf.
handler404 = "mysite.views.page_not_found"
handler500 = "mysite.views.server_error"
handler403 = "mysite.views.permission_denied"
handler400 = "mysite.views.bad_request"Here, each variable points to a Python function (view) that will handle the corresponding error.
NOTE
All error handler functions receive an HttpRequest object and an Exception object, except the handler for 500, which receives only the HttpRequest.
Conflict Resolution
If both a custom template and a handler variable exist, the handler variable takes priority. Django will run your custom view function, and it is then up to that function to decide which template to render.
Behavior During Development
Custom error handlers are only used when DEBUG = False in your settings. In development (DEBUG = True), Django displays detailed debug pages to help you identify and fix errors.
