Form Templates¶
The {% form %} template tag renders a form bound to a registered action, and this page covers every shape of the tag, the variables it publishes, and the rendering patterns for single and multi form pages.
The form Tag¶
The block form is the standard shape.
{% form @action="create_note" %}
{{ form.title }}
{{ form.body }}
<button type="submit">Save</button>
{% endform %}
The tag does six things.
Looks up the action name in the registry and resolves its UID.
Emits a
<form method="post">element withactionset to/_next/form/<uid>/.Emits a hidden
csrfmiddlewaretokeninput with the current CSRF token.Emits a hidden
_next_form_pagefield with the absolute path to the currentpage.py.Emits a hidden
_next_form_originfield set torequest.pathverbatim, consumed byredirect_to_origin.Publishes a
formvariable inside the block, either the unbound form on a GET or the bound form on a re-rendered failure.
The Action Reference¶
The @action argument names the action.
It accepts either a plain name or a namespaced name.
{% form @action="create_note" %}...{% endform %}
{% form @action="notes:save" %}...{% endform %}
The bare action="..." spelling without the @ is also accepted and equivalent, with @action being the recommended spelling.
An opening tag without an @action argument raises TemplateSyntaxError at parse time.
A name that is not in the registry resolves to an empty action attribute rather than raising.
Render-Time Failures¶
The tag raises ImproperlyConfigured at render time in two cases.
- Missing
request. The tag reads
requestfrom the template context to build the CSRF token and the hidden fields. Adddjango.template.context_processors.requesttoTEMPLATES[*].OPTIONS.context_processorsso the request reaches the context.- Missing
current_page_module_path. The tag reads
current_page_module_pathto emit the_next_form_pageorigin field. The file router sets this variable on every rendered page. Render the form through the file router rather than a hand-built view so the variable is present.
The next.E019 system check, described in CSRF and Forms, catches the missing context processor before a request reaches the tag.
Class and Extra Attributes¶
Every key=value pair on the opening tag other than @action and method becomes a literal attribute on the <form> element.
The tag does not interpret extra pairs as URL parameters.
{% form @action="upload" enctype="multipart/form-data" class="card" %}
{{ form.file }}
<button type="submit">Upload</button>
{% endform %}
The dispatcher always processes POST submissions and the tag always renders method="post".
A method argument on the tag is ignored.
Captured URL Parameters¶
The tag does not need any argument to forward captured URL parameters.
At render time the tag reads request.resolver_match.kwargs and emits a hidden _url_param_<name> field for every captured kwarg, skipping the dispatch uid and any name reserved by the dependency resolver.
The captured values come from the URL converters of the rendering page.
{% form @action="update_note" %}
{{ form.title }}
<button type="submit">Save</button>
{% endform %}
A page whose URL captures id therefore posts a hidden _url_param_id field automatically.
The handler receives the same value through DUrl["id", int] or any other URL marker.
On the POST the dispatch URL captures only the action UID.
The dispatcher recovers the page URL kwargs by reading every _url_param_* field from request.POST, stripping the prefix from each name.
Each recovered value is a string from the form body, so the dispatcher tries int(value) first and falls back to the original string when the cast fails.
Declare the handler parameter as DUrl["id", int] to keep the int shape on both the initial render and the re-render, or as DUrl["id", str] to opt out of the int-first coercion.
The form Variable¶
The block body has access to a variable named form.
The framework decides what to publish based on the request lifecycle.
- Initial render on GET.
The variable is an unbound form. The framework calls
get_initialthrough the dependency resolver, then constructs the form from the returned initial data or model instance.- Re-rendered page after a failing POST.
The variable is the bound form with errors. The template renders the user input plus any field errors.
The tag does not read a form context key.
On the initial render it reads a context key named after the action that holds a SimpleNamespace with a form attribute, and falls back to building that namespace itself when the key is absent.
A namespaced action name contains : and is not addressable from the template scope, so its context lookup runs in Python only.
Customise the initial form by overriding get_initial on the form class rather than publishing a form context.
from typing import Any
from django.http import HttpRequest
from next.forms import ModelForm
from notes.models import Note
class NoteForm(ModelForm):
class Meta:
model = Note
fields = ("title", "body")
@classmethod
def get_initial(cls, request: HttpRequest, id: int | None = None) -> Note | dict[str, Any]:
if id is None:
return {}
return Note.objects.get(pk=id)
On a re-rendered failure the dispatcher always supplies the bound failing form under the action-named key. The template therefore does not need to branch on bound vs unbound.
Multiple Forms on One Page¶
Each call to {% form %} references a different action.
The dispatcher routes submissions through the URL alone, so different forms do not interfere with each other.
{% form @action="search" class="search-form" %}
{{ form.query }}
<button type="submit">Search</button>
{% endform %}
{% form @action="create_note" class="create-form" %}
{{ form.title }}
{{ form.body }}
<button type="submit">Create</button>
{% endform %}
A page can also publish multiple bound forms by registering several @context("...") functions with distinct keys.
The block always publishes the bound form under the name form, so each {% form %} block sees its own form even when two blocks render on the same page.
Manual CSRF¶
The tag emits the hidden csrfmiddlewaretoken input automatically.
Add Django’s {% csrf_token %} manually only when you build the form element without the tag, for example in a plain <form>.
Even then, the dispatcher still requires the hidden _next_form_page field so a hand crafted form must include it.
<form action="/_next/form/{{ action_uid }}/" method="post">
{% csrf_token %}
<input type="hidden" name="_next_form_page" value="{{ current_page_module_path }}">
<button type="submit">Send</button>
</form>
The current_page_module_path variable is published by the framework on every rendered page.
Rendering Field Errors¶
Errors live on the bound form. Render them inline with each field or as a list at the top of the form.
{% form @action="create_note" %}
<div>
{{ form.title }}
{% if form.title.errors %}
<p class="error">{{ form.title.errors|first }}</p>
{% endif %}
</div>
<button type="submit">Save</button>
{% endform %}
{% form @action="create_note" %}
{% if form.errors %}
<ul class="errors">
{% for field, messages in form.errors.items %}
{% for message in messages %}<li>{{ field }} {{ message }}</li>{% endfor %}
{% endfor %}
</ul>
{% endif %}
{{ form.title }}
{{ form.body }}
<button type="submit">Save</button>
{% endform %}
Empty Form Bodies¶
A tag block with only a submit button is the confirmation pattern covered in Actions Without form_class.
Common Patterns¶
Form in a Component¶
A component template can host a {% form %} tag.
The framework injects the same current_page_module_path because the surrounding page provides it.
Form in a Layout¶
Layouts also receive current_page_module_path.
A login form rendered from the root layout therefore re-renders the original page on validation failure instead of dropping back to the layout.
Inline Form with Plain HTML¶
Use plain HTML when you need full control over markup or aria attributes.
Include both {% csrf_token %} and the hidden _next_form_page field to keep the dispatcher happy.
See Also¶
See also
Actions for the handler side of the contract. Validation and Re-render for what runs after a failing submission. Template Tags for every template tag the framework registers.