URL reversing#

next.dj generates a URL name for every file-routed page. This page covers page_reverse and with_query, the two reverse helpers exported from next.urls.

Overview#

File-routed pages register Django URL names of the form next:page_<segments>. Those names work with the standard django.urls.reverse function. page_reverse accepts the directory template instead of the computed name, and reads URL_NAME_TEMPLATE itself so it keeps working when the prefix changes. See File router for the segment-naming rules and the URL_NAME_TEMPLATE setting.

with_query composes the query string of a URL that already exists. Pass keyword arguments to add or replace query parameters, pass None to remove a key, pass a list or tuple to repeat a key.

page_reverse#

The helper takes a path template that mirrors the directory tree. Bracket parameters drop their type prefix. Captured values are passed as keyword arguments.

examples#
from next.urls import page_reverse

# routes/page.py
page_reverse()                              # "/"

# routes/blog/page.py
page_reverse("blog")                        # "/blog/"

# routes/posts/[slug]/page.py
page_reverse("posts/[slug]", slug="hello")  # "/posts/hello/"

# routes/posts/[int:post_id]/page.py
page_reverse("posts/[int:post_id]", post_id=7)  # "/posts/7/"

The template is normalised through the same parser the router uses for URL name generation. The template must repeat the same bracket text the directory uses, including any converter prefix, because the URL name is computed from the raw segment text.

The resulting segment string is fed into URL_NAME_TEMPLATE before the lookup. The default page_{name} yields next:page_posts_slug. Changing URL_NAME_TEMPLATE changes the name page_reverse resolves against without any call-site edits. See File router for the setting and the segment-naming rules.

Namespace override#

The default namespace is next, configured through next.urls.manager.app_name. page_reverse forwards namespace= straight to django.urls.reverse, so the value must name a Django URL namespace that already exists. The next namespace exists because next.urls sets app_name = "next", and a second namespace exists only when next.urls is mounted again under an explicit namespace argument.

config/urls.py#
from django.urls import include, path

urlpatterns = [
    path("", include("next.urls")),
    path("admin/", include("next.urls", namespace="admin")),
]

With that second mount in place, page_reverse can target it.

secondary namespace#
page_reverse("dashboard", namespace="admin")  # "/admin/dashboard/"

The /admin/ prefix comes from the path("admin/", ...) mount, not from the namespace argument. page_reverse only selects which namespace reverse resolves against.

Passing a namespace that no Django mount registers raises NoReverseMatch.

When to use page_reverse instead of reverse#

django.urls.reverse("next:page_posts_slug", kwargs={"slug": "hello"}) and page_reverse("posts/[slug]", slug="hello") are equivalent. Use page_reverse when the call site references the directory tree, reverse when the call site already has the URL name in a variable.

The lazy variant#

page_reverse_lazy takes the same arguments and defers the resolution until the value is first coerced to str. Use it in positions evaluated at class-definition time, before the URLconf is ready, such as Meta.success_url on a form class. It pairs with page_reverse the way Django pairs reverse() with reverse_lazy().

class-level URL#
from attachments.models import Attachment
from next.forms import ModelForm
from next.urls import page_reverse_lazy

class AttachmentForm(ModelForm):
    class Meta:
        model = Attachment
        fields = ("title", "file")
        success_url = page_reverse_lazy("attachments")

with_query#

The helper takes a URL string and adds, replaces, or removes query parameters.

query manipulation#
from next.urls import with_query

with_query("/search/", query="next.dj")
# "/search/?query=next.dj"

with_query("/search/?query=django", query="next.dj")
# "/search/?query=next.dj"

with_query("/search/?query=django&page=2", page=None)
# "/search/?query=django"

with_query("/filter/", tag=["python", "django"])
# "/filter/?tag=python&tag=django"

# An empty-valued parameter is preserved unless you pass that key.
with_query("/search/?flag=", query="next.dj")
# "/search/?flag=&query=next.dj"

with_query preserves blank-valued keys such as flag= because it parses the existing query with blank values kept. Pass the key explicitly to change or drop it.

Multi value keys#

Passing a list or tuple repeats the key in the output. A scalar value replaces every existing entry for that key.

replace vs repeat#
with_query("/filter/?tag=python&tag=django", tag="rust")
# "/filter/?tag=rust"

with_query("/filter/?tag=python", tag=["python", "django"])
# "/filter/?tag=python&tag=django"

Combining page_reverse and with_query#

Compose both helpers when the base URL needs both reversal and query parameters.

filtered listing#
from next.urls import page_reverse, with_query

def filtered_notes_url(*, tag: str | None = None, page: int = 1) -> str:
    base = page_reverse("notes")
    return with_query(base, tag=tag, page=page)

filtered_notes_url(tag="python", page=2)
# "/notes/?tag=python&page=2"

filtered_notes_url(tag=None, page=1)
# "/notes/?page=1"

The None value drops the key entirely. This is convenient for building pagination links where the current filter may or may not be set.

Common patterns#

Redirect after action#

An action handler can return an HttpResponseRedirect to a reversed page URL.

notes/pages/page.py#
from django.http import HttpRequest, HttpResponseRedirect
from next.forms import ModelForm
from next.urls import page_reverse
from notes.models import Note

class CreateNoteForm(ModelForm):
    class Meta:
        model = Note
        fields = ("title", "body")

    def on_valid(self, request: HttpRequest) -> HttpResponseRedirect:
        self.save()
        return HttpResponseRedirect(page_reverse())

Pagination#

with_query is the natural home for pagination links because each page changes one parameter.

pagination helper#
from next.urls import with_query

def page_link(request, page: int) -> str:
    return with_query(request.get_full_path(), page=page)

Testing#

with_query is a pure stdlib helper. It accepts a URL string and keyword arguments and returns a string, so a unit test can call it without configuring Django.

with_query test#
from next.urls import with_query

def test_with_query_drops_none() -> None:
    assert with_query("/?a=1", a=None) == "/"

page_reverse calls into the Django URL resolver, so the test process needs configured Django settings and a loaded URL configuration before the helper resolves anything. Use pytest-django or call django.setup() once, then assert against the produced path.

page_reverse test#
from next.urls import page_reverse

def test_page_reverse_empty() -> None:
    assert page_reverse() == "/"

Reading the query string back#

The helpers on this page write query strings. To read them in a page or component, annotate a parameter with the DQuery[T] marker. with_query and DQuery are two ends of the same wire.

DQuery[list[T]] accepts several wire formats for a repeated parameter, and with_query emits the repeated-key form when you pass a list. To read a repeated parameter outside the resolver, call get_multi_values(request, name) from next.urls, which returns every value for that key as a list. Captured path segments are separate. They flow through DUrl or plain URL kwargs as described in Dependency injection.

See Read query parameters for the full typed-query walkthrough.

See also#

See also

File router for the URL name format and route shape. Reverse URLs for a recipe-shaped walkthrough. URLs reference for the full API.