skip to content

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%

answer

  1. what converters cannot express
  2. captures lose their type
  3. named versus unnamed groups
  4. anchors are your job
  5. the removed regex function

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.

solid answer

~40 s

`path()` takes literal text with `<converter:name>` placeholders, escapes the rest and anchors it; `re_path()` takes a Python regex you write yourself. Reach for it for alternation such as `(?P<fmt>pdf|png)`, exact widths like `[0-9]{4}`, optional segments, or legacy URL shapes. The trade-off is the arguments: every capture is a `str` because no `to_python()` runs, so `<int:year>` becoming `(?P<year>[0-9]{4})` turns `2026` into `'2026'`. Named groups become keyword arguments; unnamed groups become positional arguments only when the regex has no named groups, otherwise they are dropped. Django does not anchor the regex for you, so start with `^` and end with `$`. `re_path()` replaced `url()`, which was removed in Django 4.0; a constraint used in several routes is usually better as a custom converter.

code

python · 7 lines
python
from django.http import HttpRequest, HttpResponse


# re_path(r'^tickets/(?P<code>[0-9a-f]{8})/(?P<fmt>pdf|png)/$', ticket_download)
def ticket_download(request: HttpRequest, code: str, fmt: str) -> HttpResponse:
    content_type = 'application/pdf' if fmt == 'pdf' else 'image/png'
    return HttpResponse(b'', content_type=content_type)

go deeper

for a junior

Recall that re_path() takes a regular expression while path() takes converter syntax, and that re_path() captures always arrive as strings.

for a middle

Explain named versus unnamed groups, why anchors matter, the url() removal in 4.0, and how switching a route between the two changes the view's argument types.

for a senior

Judge when a regex should become a custom converter, and catch the migration traps: regexes pasted into path(), missing anchors, and views that relied on int arguments.

for a principal

Set a convention for a large URLconf, such as path() by default and regexes only with a comment and a test, so routing stays readable as teams grow.

## Two ways to write a route `django.urls.path()` takes a **route**: literal text plus `<converter:name>` placeholders. Django escapes everything outside the brackets, so a dot in `'feeds/events.ics'` means a literal dot, and it anchors the compiled expression so the route must match the whole remaining path. `django.urls.re_path()` takes a **Python regular expression** that you write, with the full `re` syntax: alternation, repetition counts, optional groups and lookaheads. Both functions take the same `(route, view, kwargs=None, name=None)` arguments and both can sit in one `urlpatterns` list. ## When re_path() earns its place - **Alternation**: `(?P<fmt>pdf|png)` for a ticket download offered in two formats. - **Exact widths**: `(?P<year>[0-9]{4})` rejects `10000`, which `<int:year>` would accept. - **Optional segments**: `^events/(?:page-(?P<page>[0-9]+)/)?$`, with a non-capturing outer group so only the number is passed. - **Legacy URLs** from an older site that must keep an irregular shape. If the same constraint recurs across routes, a **custom path converter** gives the same power with `path()` syntax and typed arguments, and is usually the better long-term choice. ## What changes for the view | Aspect | `path()` | `re_path()` | |---|---|---| | Syntax | literal text and `<conv:name>` | Python regex | | Argument types | converted by `to_python()` (`int`, `UUID`, ...) | always `str` | | Keyword or positional | always keyword | named groups as keywords; unnamed groups positional only when no named group exists | | Anchoring | automatic | write `^` and `$` yourself | | Regex characters in the route | escaped, matched literally | interpreted | The details behind the table: 1. **Captured values are strings.** `re_path(r'^events/(?P<year>[0-9]{4})/$', views.year_archive)` calls `year_archive(request, year='2026')`. Switching a route from `<int:year>` to a regex silently changes `year` from `int` to `str`, and a view that does arithmetic or compares with an integer breaks. 2. **Unnamed groups are positional, sometimes.** With no named groups, each unnamed group becomes a positional argument. When a regex mixes both styles, the unnamed groups are ignored and only named ones are passed. Django's documentation recommends one style per regex. 3. **Nested captures are passed too.** `^blog/(page-([0-9]+)/)?$` passes both `'page-2/'` and `'2'`. Use `(?:...)` for groups that should not capture. 4. **Anchors are your job.** Without a leading `^`, the regex may match in the middle of the path; without a trailing `$`, extra trailing text is accepted and silently ignored. ```python from django.urls import path, re_path from . import views urlpatterns = [ path('events/<int:year>/', views.year_archive), # year is an int re_path( r'^tickets/(?P<code>[0-9a-f]{8})/(?P<fmt>pdf|png)/$', views.ticket_download, # code and fmt are str ), ] ``` ## Migrating from url() `django.conf.urls.url()` was the regex-based routing function of early Django; it was removed in Django 4.0 and `re_path()` is its replacement with the same regex semantics. The classic migration mistake is pasting a regex into `path()`: - `path(r'^events/$', ...)` is taken literally and matches only a path that contains the characters `^` and `$`. - Django's system check `2_0.W001` warns when a `path()` route contains `(?P<`, begins with `^` or ends with `$`, precisely to catch this. - A `re_path()` pattern that begins with `/` triggers `urls.W002` while `APPEND_SLASH` is on, because the leading slash is never part of the matched text. ## Choosing - Prefer `path()` for readability and typed arguments. - Use `re_path()` for a one-off shape that converters cannot express. - Promote a regex you repeat into a custom converter and go back to `path()`. - When switching a route between the two, re-check the view's parameter types and its tests. ## A worked conversion Suppose the ticketing site has a legacy regex route for session pages, `re_path(r'^events/(?P<slug>[-\w]+)/sessions/(?P<number>[0-9]+)/$', views.session_detail)`. Moving it to `path('events/<slug:slug>/sessions/<int:number>/', views.session_detail)` changes three things at once: 1. `number` arrives as an `int`, so code such as `int(number)` in the view becomes redundant, and code such as `number.zfill(2)` breaks. 2. `slug` becomes stricter: `\w` in a Python regex matches Unicode word characters, while the `slug` converter accepts ASCII letters, digits, hyphens and underscores only, so an event slug with accented letters stops matching. 3. The route is anchored and escaped for you, so a forgotten `$` can no longer let extra trailing text through. A test that requests one existing URL per converted route, including a non-ASCII slug if the data allows them, catches all three.

  • In Django, how do you pass a fixed extra value to a view from a URL pattern?
    Both `path()` and `re_path()` accept a `kwargs` dictionary as their third argument, for example `path('events/', views.event_list, {'upcoming': True})`. Django merges it into the keyword arguments, and a key present in both wins from the dictionary rather than from the URL capture.
  • What goes wrong if a Django re_path() regex omits the trailing $?
    An endpoint regex without `$` is matched with a search, and any text after the match is ignored. `^events/(?P<slug>[-\w]+)/` would also accept `/events/gala/anything/else`, so several URLs serve one page and a later, more specific route may never be reached.

saying these in an interview costs you the question

  • re_path() converts a digits-only group to an int for the view.
  • Django automatically anchors re_path() regexes at both ends.
  • When a regex mixes named and unnamed groups, the view receives both.
  • path() understands regex syntax, so ^ and $ work inside its route string.
  • url() is still available in current Django as an alias for re_path().