WTForms
While you can create forms with raw HTML, WTForms (with Flask-WTF) is much better. It handles form validation, CSRF protection, and rendering automatically. You define form classes in Python, and they become easy to use in your templates.
First, install Flask-WTF: pip install flask-wtf. Then define your form class:
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Length, Email, EqualTo
class LoginForm(FlaskForm):
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired(), Length(min=6)])
confirm_password = PasswordField('Confirm Password',
validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('Login')
Each field has validators that check the data. DataRequired ensures the field isn't empty, Email checks for valid email format, Length enforces minimum/maximum length, and EqualTo checks that two fields match.
In your template, WTForms makes rendering super easy. You don't write HTML inputs manually - the form object does it for you:
<form method="POST" action="">
{{ form.hidden_tag() }}
<div class="form-group">
{{ form.email.label }}
{{ form.email(class="form-control") }}
{% for error in form.email.errors %}
<span class="error">{{ error }}</span>
{% endfor %}
</div>
<div class="form-group">
{{ form.password.label }}
{{ form.password(class="form-control") }}
{% for error in form.password.errors %}
<span class="error">{{ error }}</span>
{% endfor %}
</div>
{{ form.submit(class="btn btn-primary") }}
</form>
See how clean that is? form.hidden_tag() adds the CSRF token automatically. form.email renders the input field. form.email.errors contains any validation messages.
In your route, you create the form instance and validate it:
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
# Form is valid, process the data
email = form.email.data
password = form.password.data
# Do login logic...
return redirect(url_for('dashboard'))
return render_template('login.html', form=form)
form.validate_on_submit() checks if the form was submitted AND passes all validators. If it returns True, you can safely use form.field.data. If False, the template shows errors automatically.
WTForms is way better than raw HTML forms because it handles validation in one place (Python), protects against CSRF attacks, and generates clean HTML. You write less code and get more features.
Try it Yourself ->