Rendering Forms in Templates
You can render forms manually (recommended for control) or use quick rendering.
Manual rendering (recommended)
Section titled “Manual rendering (recommended)”Template example:
<form method="post">
{{ form.hidden_tag() }}
<div>
{{ form.name.label }}
{{ form.name() }}
{% for error in form.name.errors %}
<p class="error">{{ error }}</p>
{% endfor %}
</div>
<div>
{{ form.email.label }}
{{ form.email() }}
{% for error in form.email.errors %}
<p class="error">{{ error }}</p>
{% endfor %}
</div>
{{ form.submit() }}
</form>Adding HTML attributes
Section titled “Adding HTML attributes”{{ form.name(class_="input", placeholder="Your name") }}Note: use class_ (underscore) because class is a Python keyword.
Keeping templates clean
Section titled “Keeping templates clean”As forms grow, you can move repeated field rendering into a partial:
templates/partials/_field.html
Then include it for each field.
A field renders itself
Section titled “A field renders itself”You do not write the <input> tags. Calling the field produces the HTML, with the id,
name, type and any validator-derived attributes already correct:
<form method="post">
{{ form.hidden_tag() }}
{{ form.name.label }} {{ form.name(class_="input") }}
<button type="submit">Sign up</button>
</form>Measured output for each field:
<input id="name" minlength="2" name="name" required type="text" value="">
<input id="pw" name="pw" type="password" value="">
<textarea id="bio" name="bio"></textarea>
<input id="age" name="age" type="number" value="">
<input id="agree" name="agree" type="checkbox" value="y">
<select id="role" name="role"><option value="u">User</option>...</select>
<label for="name">Your name</label>Because id and the label’s for match, clicking the label focuses the input — an
accessibility win you get without thinking about it.
hidden_tag() is not optional
Section titled “hidden_tag() is not optional” flowchart TD
T["{{ form.hidden_tag() }}"] --> C["renders the hidden csrf_token input"]
C --> S["signed with SECRET_KEY"]
M["forgetting it"] --> E["every POST fails with
'The CSRF token is missing.'"]
<input id="csrf_token" name="csrf_token" type="hidden" value="ImUzNjI0ZTdmNWY0Mm...">It renders every hidden field on the form, the CSRF token included. Leaving it out is the most common reason a form that “looks fine” never submits successfully.
Showing errors next to the field
Section titled “Showing errors next to the field”<div class="field">
{{ form.name.label }}
{{ form.name(class_="input") }}
{% for error in form.name.errors %}
<span class="error">{{ error }}</span>
{% endfor %}
</div>Measured with name='a' submitted:
<span class='err'>Field must be at least 2 characters long.</span>field.errors is a plain list, empty before validation and on success — so the loop
simply produces nothing when there is nothing to say.
Looping the whole form
Section titled “Looping the whole form”{% for field in form if field.widget.input_type != 'hidden' %}
<div class="field">
{{ field.label }} {{ field() }}
{% for e in field.errors %}<span class="error">{{ e }}</span>{% endfor %}
</div>
{% endfor %}A FlaskForm is iterable, so a macro like this renders any form you pass it. Measured,
the loop yields the labels in declaration order: Your name, Password, Bio, Age,
Agree, Role. Filtering out hidden inputs keeps the CSRF token from getting a label
and a wrapper div of its own — render it separately with hidden_tag().
See it move
Section titled “See it move”Check yourself
Section titled “Check yourself”-
What does form.hidden_tag() render, and what breaks without it?
It renders every hidden field, the signed csrf_token included. Omitting it is the most common reason a form that looks correct never validates.
pch.quizShowAnswer
B — the hidden CSRF token input; without it every POST fails with 'The CSRF token is missing.' — It renders every hidden field, the signed csrf_token included. Omitting it is the most common reason a form that looks correct never validates.
-
Why does clicking a field's label focus its input without any extra work?
Measured: <input id="name" ...> and <label for="name">. The matching pair is what makes the label clickable, which is an accessibility requirement you get by default.
pch.quizShowAnswer
B — wtforms renders matching id and for attributes on the input and label — Measured: <input id="name" ...> and <label for="name">. The matching pair is what makes the label clickable, which is an accessibility requirement you get by default.
-
After a successful POST, why redirect rather than render the result directly?
This is the Post/Redirect/Get pattern. Without the redirect, refreshing the result page repeats the POST.
pch.quizShowAnswer
B — so the browser reload button does not resubmit the form and duplicate the record — This is the Post/Redirect/Get pattern. Without the redirect, refreshing the result page repeats the POST.
pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading