Flask-WTF is a Flask extension (current version 1.2.2) that simplifies form handling by integrating the WTForms library. It provides robust features such as CSRF protection, form validation, file upload support, and reCAPTCHA integration. Maintained by the Pallets organization, it generally sees regular updates, though specific release cadences can vary.
Install & Compatibility
Where this runs
tested against v1.3.0 · pip install
no network on importno background threads
Install × environment matrix
Each cell = how many times install + import succeeded across repeated harness runs. Partial = flaky.
glibc = Debian/Ubuntu slim · musl = Alpine Linux
muslpy 3.10–3.930 runs
installs and imports cleanly · install 0.0s · import 0.553s · 23.6MB
glibcpy 3.10–3.930 runs
installs and imports cleanly · install 2.3s · import 0.500s · 24MB
22MB installed
● package 22MB
Code
Verified usage
Verified import paths — ran on the pinned version, not inferred.
`Form` was deprecated and removed in favor of `FlaskForm` in versions >= 1.0.0. `FlaskForm` is a Flask-specific subclass of WTForms' `Form` that includes CSRF protection.
from flask_wtf import FlaskForm
Since version 0.9.0, fields (e.g., `StringField`, `PasswordField`) must be imported directly from `wtforms`, not `flask_wtf`.
from wtforms import StringField
Validators are imported directly from `wtforms.validators`.
from wtforms.validators import DataRequired
This quickstart demonstrates a basic Flask application using Flask-WTF to create, render, and validate a simple form. It includes defining a `FlaskForm` subclass, rendering it in an HTML template, and processing its submission with `validate_on_submit()`.
import os
from flask import Flask, render_template, request, redirect, url_for
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('FLASK_SECRET_KEY', 'a_very_secret_key_for_dev')
class MyForm(FlaskForm):
name = StringField('Name', validators=[DataRequired()])
submit = SubmitField('Submit')
@app.route('/', methods=['GET', 'POST'])
def index():
form = MyForm()
if form.validate_on_submit():
name = form.name.data
print(f"Form submitted with name: {name}")
# In a real app, save 'name' to a database, etc.
return redirect(url_for('success', name=name))
return render_template('index.html', form=form)
@app.route('/success/<name>')
def success(name):
return f'Hello, {name}! Your form was submitted successfully.'
if __name__ == '__main__':
# Example template content (save as templates/index.html)
# <form method="POST" action="/">
# {{ form.hidden_tag() }}
# <p>
# {{ form.name.label }}
# {{ form.name(size=30) }}
# {% if form.name.errors %}
# <ul class="errors">
# {% for error in form.name.errors %}
# <li>{{ error }}</li>
# {% endfor %}
# </ul>
# {% endif %}
# </p>
# <p>{{ form.submit() }}</p>
# </form>
app.run(debug=True)
Upgrade
Version history
Breaking-change detection hasn't run for this library yet.
Audit
Security & dependencies
CVE tracking and dependency tree are planned for a later release.