Flask WTForms
Form validation checks that user input meets your requirements before your application processes or stores it. WTForms provides a set of built-in validators. You can also write custom validators for business-specific rules.
Why Validate on the Server
Browser-side validation (HTML required, type="email") is a courtesy to the user — it gives instant feedback. Server-side validation is the security gate — it catches malicious or malformed data that bypasses the browser. Always validate on the server even if the browser validates too.
Browser validation: Nice to have (user experience) Server validation: Mandatory (security and data integrity)
Built-In WTForms Validators
| Validator | What It Checks | Example |
|---|---|---|
DataRequired() | Field is not empty | Name field must have a value |
Email() | Valid email format | user@example.com |
Length(min=2, max=50) | String length within range | Username 2–50 characters |
EqualTo('field') | Matches another field | Password confirmation |
NumberRange(min=1, max=100) | Number within range | Age between 1 and 100 |
URL() | Valid URL format | https://example.com |
Regexp(r'^\d{5}$') | Matches a regex pattern | 5-digit ZIP code |
Optional() | Field is optional | Skips other validators if empty |
Registration Form with Validators
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, EmailField, SubmitField
from wtforms.validators import DataRequired, Email, Length, EqualTo
class RegisterForm(FlaskForm):
username = StringField('Username', validators=[
DataRequired(message='Username is required.'),
Length(min=3, max=20, message='Username must be 3–20 characters.')
])
email = EmailField('Email', validators=[
DataRequired(),
Email(message='Enter a valid email address.')
])
password = PasswordField('Password', validators=[
DataRequired(),
Length(min=8, message='Password must be at least 8 characters.')
])
confirm = PasswordField('Confirm Password', validators=[
DataRequired(),
EqualTo('password', message='Passwords must match.')
])
submit = SubmitField('Create Account')How Validators Run
User submits form
│
form.validate_on_submit() called
│
WTForms runs each validator in order for each field
│
All pass? ──YES──▶ returns True ──▶ process form
│
NO
│
field.errors list filled with error messages
│
returns False ──▶ redisplay form with errors
Displaying Errors per Field
<form method="POST">
{{ form.hidden_tag() }}
{{ form.username.label }}
{{ form.username() }}
{% for err in form.username.errors %}
<p style="color:red">{{ err }}</p>
{% endfor %}
{{ form.email.label }}
{{ form.email() }}
{% for err in form.email.errors %}
<p style="color:red">{{ err }}</p>
{% endfor %}
{{ form.password.label }}
{{ form.password() }}
{% for err in form.password.errors %}
<p style="color:red">{{ err }}</p>
{% endfor %}
{{ form.confirm.label }}
{{ form.confirm() }}
{% for err in form.confirm.errors %}
<p style="color:red">{{ err }}</p>
{% endfor %}
{{ form.submit() }}
</form>Writing a Custom Validator
Custom validators are functions that raise ValidationError when the input fails the check. Attach them to a field alongside the built-in validators:
from wtforms import ValidationError
def no_spaces(form, field):
if ' ' in field.data:
raise ValidationError('Username cannot contain spaces.')
class RegisterForm(FlaskForm):
username = StringField('Username', validators=[
DataRequired(),
Length(min=3, max=20),
no_spaces
])Method Validator on the Form Class
WTForms also recognizes methods named validate_fieldname on the form class. These run automatically after the field's listed validators:
class RegisterForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
def validate_username(self, field):
# Check if username already exists in database
existing = User.query.filter_by(username=field.data).first()
if existing:
raise ValidationError('Username already taken. Choose another.')Accessing Cleaned Data
After successful validation, field.data holds the cleaned, validated value. For string fields, WTForms strips leading and trailing whitespace automatically.
if form.validate_on_submit():
username = form.username.data # cleaned string
email = form.email.data
password = form.password.data
# now safe to save to databaseSummary
Validation protects your database from bad data and your users from confusing errors. WTForms validators run automatically when you call validate_on_submit(). Built-in validators cover the most common cases. Custom validators and validate_fieldname methods handle business-specific rules. After validation passes, access clean data through form.fieldname.data and store it with confidence.
