skip to content

Route Patterns & Converters

path() with typed converters such as int, slug and uuid, re_path() for regex routes, and custom converters from register_converter(). Interviewers test first-match ordering and trailing slashes.

part ofDjangooverview, primer and where to startread it →
on this pageshow

explore

questions

6

In Django's path() routes, what do the built-in str, int, slug, uuid and path converters match, and what does the view receive?

level: juniorimportance: must knowfreq 62%

answer

  1. five names registered by default
  2. one segment versus many segments
  3. only two change the Python type
  4. a bare <name> has a default
  5. lowercase dashed form only

basics

~20 s

str (the default) matches one non-empty segment without a slash; int matches digits and passes an int; slug matches ASCII letters, digits, hyphens and underscores; uuid matches a lowercase dashed UUID and passes uuid.UUID; path matches anything non-empty, slashes included.

solid answer

~40 s

Django registers five converters in `django.urls.converters`. `str` matches `[^/]+`, one non-empty segment, and is what a bare `<name>` means. `int` matches `[0-9]+` and calls `int()`, so the view gets an integer; a minus sign never matches. `slug` matches `[-a-zA-Z0-9_]+` and still passes a string, so Unicode slugs do not fit it. `uuid` accepts only the lowercase, dashed form and passes a `uuid.UUID`. `path` matches `.+`, slashes included, so it belongs at the end of a route. When text does not fit a converter's regex, that pattern simply does not match and Django tries the next one; if nothing matches, the request gets a 404 and the view never runs.

code

python · 13 lines
python
import uuid

from django.http import HttpRequest, HttpResponse


# path('events/<slug:slug>/sessions/<int:number>/', session_detail)
def session_detail(request: HttpRequest, slug: str, number: int) -> HttpResponse:
    return HttpResponse(f'{slug} session {number}')


# path('tickets/<uuid:code>/', ticket_detail)
def ticket_detail(request: HttpRequest, code: uuid.UUID) -> HttpResponse:
    return HttpResponse(f'ticket {code.hex}')

go deeper

for a junior

Recall the five converter names, that str is the default for a bare <name>, and that int and uuid hand the view converted values while the others pass strings.

for a middle

Explain that each converter is a regex plus to_python(), that a regex mismatch makes the pattern decline rather than error, and the leading-zero and uppercase-UUID edges.

for a senior

Choose converters for canonical URLs: uuid for public ticket codes, int for keys, and be clear that converters validate shape only, never existence or access.

for a principal

Treat URL shape as a public contract: the identifier format behind each converter decides which links stay valid, so settle slugs versus UUIDs before URLs ship.

## What a path converter is In Django, a **URLconf** is a Python module with a `urlpatterns` list. Each entry made with `django.urls.path()` takes a **route** string such as `'events/<slug:slug>/'`. Text outside the angle brackets is matched literally; each `<converter:name>` placeholder captures one value and passes it to the view as the keyword argument `name`. If you omit the converter and write `<name>`, Django uses `str`. A **path converter** decides two things for its placeholder: - which text it accepts, through a `regex` attribute; - what Python value the view receives, through its `to_python()` method. Django compiles one regular expression per route when `path()` is called, escaping the literal text and splicing in each converter's regex. At request time the captured strings go through `to_python()` before the view is called. ## The five built-in converters Django 6.1 registers five converters by default: | Converter | Regex | Example match | View receives | |---|---|---|---| | `str` | `[^/]+` | `launch-party` | `str` | | `int` | `[0-9]+` | `42` | `int` | | `slug` | `[-a-zA-Z0-9_]+` | `jazz-night_2026` | `str` | | `uuid` | lowercase, dashed 8-4-4-4-12 hex | `075194d3-6885-417e-a8a8-6c931e272f00` | `uuid.UUID` | | `path` | `.+` | `press/2026/poster.pdf` | `str` | Only `int` and `uuid` change the Python type. `slug` and `path` hand the view a plain string; they differ from `str` only in which text they accept. ## A ticketing URLconf ```python from django.urls import path from . import views urlpatterns = [ path('events/', views.event_list, name='event-list'), path('events/<slug:slug>/', views.event_detail, name='event-detail'), path('events/<slug:slug>/sessions/<int:number>/', views.session_detail, name='session-detail'), path('tickets/<uuid:code>/', views.ticket_detail, name='ticket-detail'), path('press-kit/<path:file_path>', views.press_kit_file, name='press-kit-file'), ] ``` A request for `/events/jazz-night/sessions/2/` calls `session_detail(request, slug='jazz-night', number=2)`, with `number` already an `int`. A request for `/tickets/075194d3-6885-417e-a8a8-6c931e272f00/` calls `ticket_detail(request, code=UUID('075194d3-...'))`, so the view can filter a `UUIDField` with `code` directly. ## How a route becomes a regular expression When `path('events/<slug:slug>/', ...)` runs, Django builds the expression `^events/(?P<slug>[-a-zA-Z0-9_]+)/` and, because this entry calls a view rather than including another URLconf, appends an end-of-string anchor. Two consequences are worth saying out loud in an interview: - **Literal text is escaped.** A dot or a plus sign in a `path()` route means that character, never a regex operator. - **Conversion runs both ways.** Each converter also has a `to_url()` method, used when Django builds a URL from a route name: `int` and `uuid` turn their values back into text with `str()`, and the string converters return the value unchanged. Because the regexes are fixed per converter, two routes that differ only in converter type, such as `events/<int:pk>/` and `events/<slug:slug>/`, can coexist: a numeric segment fits both, so list order decides which one answers it. ## Edge cases interviewers probe - **`<slug>` is not the slug converter.** The word inside the brackets is the argument name; the converter is what comes before the colon. `<slug>` means `str`, which also accepts dots, spaces and non-ASCII characters. - **`int` accepts only non-negative digits.** `-5` does not match. Leading zeros do: `/sessions/007/` passes `7`, so two URLs reach one page, while reversing the route writes `7`. - **`uuid` is strict.** Uppercase letters, missing dashes or braces do not match. That keeps one canonical URL per ticket, but a pasted uppercase code gets a 404. - **`slug` is ASCII-only.** A value from `SlugField(allow_unicode=True)`, such as an event name in Cyrillic, will not match `<slug:...>`; use `str` or a custom converter. - **`path` is greedy.** It swallows slashes, so it belongs at the end of a route and below narrower routes that share its prefix. - **None of them touch the database.** `<int:pk>` proves the text is digits, not that an event with that key exists; the view still looks the row up and returns a 404 for a miss. ## What happens when the text does not fit 1. Django tries the entries of `urlpatterns` in order. 2. If a converter's regex rejects the segment, that pattern does not match. No error is raised; the next pattern is tried. 3. If no pattern matches, resolution fails and the request gets Django's 404 response. The view is never called. This is why converters double as cheap input validation: `/tickets/not-a-uuid/` never reaches `ticket_detail`, so the view can trust the type of `code`. ## Choosing a converter - `int` for integer primary keys and ordinal numbers. - `slug` for human-readable identifiers stored in a `SlugField`. - `uuid` for public, hard-to-guess identifiers such as ticket codes. - `path` only when the value genuinely contains slashes, like a file path. - `str` for free-form single segments, and a custom converter when none of these fits.

  • What does Django's int converter do with /events/jazz-night/sessions/007/?
    It matches, because `[0-9]+` accepts leading zeros, and `int('007')` passes `7` to the view. Reversing the same route writes `7`, so `/sessions/007/` and `/sessions/7/` serve one page. If canonical URLs matter, redirect the padded form in the view or use a custom converter whose regex rejects leading zeros.
  • Why should a Django path converter usually sit at the end of a route?
    Its regex is `.+`, which crosses slashes, so it absorbs every remaining segment. Placed mid-route, the literal text after it becomes ambiguous; placed in an early entry, it can capture URLs meant for narrower routes listed after it. Put `<path:...>` last in the route and its route near the bottom of the list.
  • Does <int:pk> in a Django route guarantee the object exists?
    No. Converters only check and convert text; they run no query. The view still fetches the row, typically with `get_object_or_404()`, and returns a 404 when it is missing. The converter's value is that the view receives an `int` and never sees non-numeric input.

saying these in an interview costs you the question

  • Writing <slug> in a route makes Django apply the slug converter.
  • Django's int converter also matches negative numbers such as -5.
  • The uuid converter accepts uppercase letters and UUIDs without dashes.
  • The slug and path converters hand the view special objects rather than strings.
  • A converter confirms that a matching database row exists before the view runs.
open as a page

In a Django URLconf listing path('events/<slug:slug>/') before path('events/new/'), which view serves /events/new/, and why?

level: juniorimportance: must knowfreq 55%

basics

~20 s

The slug route serves it: Django tries urlpatterns in list order and calls the first pattern that matches, and 'new' is a valid slug. Listing the fixed events/new/ route above the converter route fixes it.

open as a page

In Django, with APPEND_SLASH at its default, what happens to a request for /events/launch-party when only events/<slug:slug>/ is routed?

level: middleimportance: should knowfreq 47%

basics

~10 s

APPEND_SLASH defaults to True, so CommonMiddleware replaces the 404 with a 301 redirect to /events/launch-party/, because the slashless path matches nothing and the slashed one resolves. Without CommonMiddleware the setting does nothing.

open as a page

In Django, when would you reach for re_path() instead of path(), and how does switching change the arguments your view receives?

level: middleimportance: should knowfreq 44%

basics

~20 s

Use re_path() when a segment needs a constraint that path() converters cannot express and a custom converter is not worth writing. Its captures reach the view as strings: named groups as keyword arguments, unnamed groups positionally.

open as a page

On a Django ticketing site, why is a custom path converter whose to_python() loads the Event row from the database a risky design?

level: seniorimportance: should knowfreq 20%

basics

~20 s

to_python() runs during URL resolution, outside the view: a missing row raises DoesNotExist, which is not ValueError and becomes a 500; it cannot see the request to filter by user; and under ASGI a sync query there raises SynchronousOnlyOperation.

open as a page

How do you write and register a custom path converter in Django, and what does raising ValueError in its to_python() or to_url() do?

level: middleimportance: nice to knowfreq 28%

basics

~20 s

Write a class with a regex attribute, to_python() and to_url(), then call django.urls.register_converter(cls, 'name') before any path() uses name:.... ValueError in to_python() makes that pattern not match; in to_url() it makes reverse() skip the pattern.

open as a page