Form Classes & Built-In Field Validators0%

Form Classes & Built-In Field Validators

Beginner12 min read•Updated: Jul 11, 2026
Study MaterialsCheatsheet

Form Classes & Built-In Field Validators

Manually writing <input> tags, extracting values from request.form, typecasting strings, and writing dozens of if not email: checks is tedious and error-prone. WTForms allows you to declare forms as Python classes, providing automatic validation, casting, and error aggregation.


1. Declaring a Form Class

Create a forms.py file and subclass flask_wtf.FlaskForm:

Python
from flask_wtf import FlaskForm
from wtforms import (
StringField,
PasswordField,
BooleanField,
SubmitField,
SelectField,
TextAreaField
)
from wtforms.validators import (
DataRequired,
Length,
Email,
EqualTo,
Regexp
)
 
class RegistrationForm(FlaskForm):
username = StringField(
label="Username",
validators=[
DataRequired(message="Username is required."),
Length(min=3, max=25, message="Username must be between 3 and 25 characters."),
Regexp(r'^[a-zA-Z0-9_]+$', message="Username must contain only letters, numbers, and underscores.")
]
)
email = StringField(
label="Email Address",
validators=[
DataRequired(message="Email is required."),
Email(message="Please provide a valid email address.")
]
)
password = PasswordField(
label="Password",
validators=[
DataRequired(),
Length(min=8, message="Password must be at least 8 characters long.")
]
)
confirm_password = PasswordField(
label="Confirm Password",
validators=[
DataRequired(),
EqualTo("password", message="Passwords must match.")
]
)
track = SelectField(
label="Specialization Track",
choices=[
("fullstack", "Full-Stack Web Development"),
("data_ai", "Data Science & AI"),
("cloud", "Cloud & DevOps")
],
validators=[DataRequired()]
)
terms = BooleanField(
label="I agree to the Terms of Service",
validators=[DataRequired(message="You must accept the terms.")]
)
submit = SubmitField(label="Create Account")

2. Handling Forms in the View Function

In your route, instantiate the form and call form.validate_on_submit():

Python
from flask import Flask, render_template, redirect, url_for, flash
from forms import RegistrationForm
 
app = Flask(__name__)
app.config["SECRET_KEY"] = "super-secret-key-change-in-production"
 
@app.route("/register", methods=["GET", "POST"])
def register():
form = RegistrationForm()
# validate_on_submit() checks:
# 1. Is this an HTTP POST request?
# 2. Is the CSRF token valid?
# 3. Do all field validators pass?
if form.validate_on_submit():
user_name = form.username.data
user_email = form.email.data
# In a real app: save user to database with hashed password
flash(f"Account successfully created for {user_name}!", "success")
return redirect(url_for("login"))
# On GET request or failed validation, re-render form with errors
return render_template("register.html", form=form)

3. Rendering the Form in Jinja2

WTForms fields are callable objects. Invoking {{ form.field_name() }} generates the complete <input> HTML element:

HTML
<!-- templates/register.html -->
{% extends "base.html" %}
 
{% block content %}
<div class="max-w-md mx-auto bg-white p-8 rounded-2xl shadow-sm border border-gray-100">
<h2 class="text-2xl font-bold mb-6 text-gray-900">Create Your Account</h2>
<form method="POST" action="">
<!-- Automatically renders hidden CSRF input token -->
{{ form.hidden_tag() }}
<!-- Username Field -->
<div class="mb-4">
{{ form.username.label(class="block text-sm font-medium text-gray-700 mb-1") }}
{{ form.username(class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-indigo-500", placeholder="johndoe") }}
{% for error in form.username.errors %}
<p class="text-red-500 text-xs mt-1">{{ error }}</p>
{% endfor %}
</div>
<!-- Email Field -->
<div class="mb-4">
{{ form.email.label(class="block text-sm font-medium text-gray-700 mb-1") }}
{{ form.email(class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-indigo-500", placeholder="you@example.com") }}
{% for error in form.email.errors %}
<p class="text-red-500 text-xs mt-1">{{ error }}</p>
{% endfor %}
</div>
 
<!-- Password Field -->
<div class="mb-4">
{{ form.password.label(class="block text-sm font-medium text-gray-700 mb-1") }}
{{ form.password(class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-indigo-500") }}
{% for error in form.password.errors %}
<p class="text-red-500 text-xs mt-1">{{ error }}</p>
{% endfor %}
</div>
<!-- Submit Button -->
<div class="mt-6">
{{ form.submit(class="w-full py-2.5 bg-indigo-600 hover:bg-indigo-700 text-white font-bold rounded-lg cursor-pointer transition") }}
</div>
</form>
</div>
{% endblock %}

Interactive Quiz

Practice Quiz

Question 1
What three checks does form.validate_on_submit() execute automatically?
Question 2
How is the hidden CSRF input field rendered in templates when using FlaskForm?
Question 3
Which validator guarantees that two password fields match during registration?
Question 4
How do you extract the sanitized, type-converted value of a field inside a view function?
Question 5
How are validation errors accessed for an individual field in a Jinja2 template?
Next Lesson

Custom Form Validators & Secure File Uploads

Continue learning with hands-on practice, examples, and exercises in the upcoming topic.

Practice Quiz

Test your understanding of this lesson with 5 questions. Each question has one correct answer.

PrevNext