skip to content

In a Django template form, what do {% url %} and {% csrf_token %} output, and what happens when either cannot produce its value?

level: juniorimportance: should knowfreq 52%

answer

  1. a path from a route name
  2. a hidden form field
  3. error page versus silent variant
  4. needs the request in the context

basics

~20 s

{% url 'name' args %} outputs the path of a named URL pattern and raises NoReverseMatch if reversing fails, unless written with as var. {% csrf_token %} outputs a hidden csrfmiddlewaretoken input, or nothing when the context lacks a token.

solid answer

~40 s

`{% url 'article-edit' article.pk %}` reverses a named URL pattern into an absolute path such as `/articles/42/edit/`, so templates never hardcode paths; arguments can be positional or keyword but not mixed. If the name or arguments do not match, the tag raises `NoReverseMatch` and the page fails to render; the variant `{% url 'name' as the_url %}` stores the result instead and yields an empty value without raising, which suits optional links. `{% csrf_token %}` renders `<input type="hidden" name="csrfmiddlewaretoken" value="...">` inside a POST form. It needs `csrf_token` in the context, which the CSRF context processor supplies when the template is rendered with the request, as `render()` does; without it the tag outputs nothing and, with `DEBUG` on, warns that `RequestContext` was probably not used.

code

django · 7 lines
django
<form method="post" action="{% url 'article-edit' pk=article.pk %}">
  {% csrf_token %}
  {{ form }}
  <button type="submit">Save</button>
</form>
{% url 'article-history' pk=article.pk as history_url %}
{% if history_url %}<a href="{{ history_url }}">History</a>{% endif %}

go deeper

for a junior

Use {% url %} with a route name instead of hardcoded paths, and put {% csrf_token %} inside every POST form that targets your own site.

for a middle

Explain NoReverseMatch versus the silent as form, and why the token needs a request-based render such as render() or render_to_string with request.

for a senior

Diagnose 403s from forms rendered without the request, and keep the loud url form in templates so broken routes fail tests instead of shipping dead links.

for a principal

Treat route names as a stable internal API: naming conventions and namespaces make templates resilient to URL redesigns.

## Two tags every form uses A typical Django edit form combines both tags: ```django <form method="post" action="{% url 'article-edit' article.pk %}"> {% csrf_token %} {{ form }} <button type="submit">Save</button> </form> ``` `{% url %}` builds the `action` path; `{% csrf_token %}` adds the hidden field the CSRF protection checks on submission. ## `{% url %}`: paths from names The first argument is a **URL pattern name**, a quoted literal or a variable. The remaining arguments fill the pattern's parameters, either positionally or by keyword, but **not mixed in one call**: ```django {% url 'article-detail' article.pk %} {% url 'article-detail' pk=article.pk %} {% url 'blog:archive' year=2026 month=9 %} ``` The output is an **absolute path without the domain**, for example `/articles/42/`, with special characters encoded. Benefits: - **One place to change a route.** Rename `articles/` to `posts/` in the URLconf and every link follows. - **Errors surface early.** A link to a view that no longer exists fails loudly instead of producing a dead link. ## When `{% url %}` cannot reverse | Form | Name or arguments do not match | |---|---| | `{% url 'name' arg %}` | raises `NoReverseMatch`; the page shows an error (a server error in production) | | `{% url 'name' arg as the_url %}` | stores an empty value in `the_url`, no exception | The `as` form is documented for **optional links**: ```django {% url 'beta-dashboard' as beta_url %} {% if beta_url %}<a href="{{ beta_url }}">Try the beta</a>{% endif %} ``` The variable created with `as` is scoped to the `{% block %}` the tag appears in. Using the silent form everywhere is a mistake: it hides broken links that the default form would have caught in the first test that renders the page. ## `{% csrf_token %}`: the hidden field Inside a form that posts to your own site, `{% csrf_token %}` renders: ```html <input type="hidden" name="csrfmiddlewaretoken" value="..."> ``` The tag reads a `csrf_token` variable from the context. That variable is supplied by the `django.template.context_processors.csrf` processor, which runs when the template is rendered with a **request**, for example via `render(request, ...)`, a class-based view or `render_to_string(..., request=request)`. What happens when the token is missing: 1. The tag outputs **nothing**. 2. With `DEBUG = True`, it emits a warning that the context did not provide the value, "usually caused by not using RequestContext". 3. The form then fails CSRF verification when submitted, with a 403 response. Common causes are rendering with `render_to_string()` without `request=`, or building a `Context` by hand. Inclusion tags copy `csrf_token` from the parent context into their own template, so a form rendered by an inclusion tag still works. ## Placement rules - **Only in forms that POST to your own site.** Do not put the token in forms that submit to external URLs, where it would leak the value to a third party. - **Not needed for GET forms.** Safe methods are not CSRF-checked, and the token would appear in the query string. - **Once per form** is enough; the tag can appear in any number of forms on the page. ## Diagnosing the two failure modes | Symptom | Likely cause | Fix | |---|---|---| | `NoReverseMatch` while rendering | route renamed, wrong namespace, missing or extra argument | fix the name or arguments; add a render test for the page | | link silently missing | `{% url ... as var %}` with a broken name | switch to the plain form where the link is not optional | | 403 on submit, form looks fine | `{% csrf_token %}` rendered nothing | render with the request; check the page source for the hidden input | | 403 only for some pages | a fragment rendered via `render_to_string()` without `request=` | pass the request through | Checking the page source is the fastest test for the CSRF case: if there is no `csrfmiddlewaretoken` input inside the form, the problem is in rendering, not in the middleware. ## What these tags do not decide `{% url %}` only asks the URL resolver; which names, namespaces and converters exist is a matter of the URLconf. `{% csrf_token %}` only prints the field; how the token is generated, rotated and verified is the middleware's job. In an interview, be ready to name the tag's output and failure mode precisely, then hand over to routing or CSRF protection for the rest.

  • Why does a form rendered with render_to_string() fail CSRF checks?
    The token reaches the template through the CSRF context processor, and context processors only run when the template is rendered with a request. `render_to_string("form.html", ctx)` without `request=request` leaves `csrf_token` out of the context, so `{% csrf_token %}` outputs nothing and the POST is rejected with 403. Pass `request=request`.
  • Can the URL name in {% url %} come from a variable?
    Yes. The first argument is resolved like any other: a quoted literal is used as-is, and an unquoted name is looked up in the context, so `{% url link.route_name link.pk %}` works. Quoting matters: `{% url home %}` without quotes treats `home` as a context variable to resolve, not as the route name.

saying these in an interview costs you the question

  • {% url %} returns a full URL including the domain
  • {% url %} silently outputs an empty string when the route is missing
  • {% csrf_token %} is needed in GET forms too
  • {% csrf_token %} generates the token itself, so the context does not matter
  • Positional and keyword arguments can be mixed in one {% url %} call