Skip to content

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_URLCONF setting.
  • Next, Django loads that Python module and looks for a variable named urlpatterns. This variable is a sequence of path() or re_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 HttpRequest instance 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:

python
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.

ConverterDescription
strMatches any non-empty string, excluding the path separator /. This is the default if you do not include a converter.
intMatches zero or any positive integer. Returns an int.
slugMatches any slug string consisting of ASCII letters or numbers, plus the hyphen and underscore characters.
uuidMatches a formatted UUID. To prevent multiple URLs from mapping to the same page, dashes must be included and letters must be lowercase.
pathMatches 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.

python
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.

python
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), where name is the name of the group and pattern is 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.
python
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:

python
# 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.

python
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 to blog.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:

python
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.

python
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.

python
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.

python
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.

python
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.

python
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:

python
# 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.
python
app_name = "blog"

urlpatterns = [
    path("", views.index, name="index"),
]
  • In the project's root URLconf where the app is included.
python
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:

python
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:

ErrorMeaning
404Page not found (The requested URL did not match any URL pattern)
500Server error (An unexpected error occurred while processing the request)
403Permission denied (The user does not have permission to access the resource)
400Bad 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.

python
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.