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.95 runs
installs and imports cleanly · install 0.0s · import 0.544s · 23.7MB
glibcpy 3.10–3.95 runs
installs and imports cleanly · install 2.4s · import 0.504s · 24MB
22MB installed
● package 22MB
Code
Verified usage
Verified import paths — ran on the pinned version, not inferred.
FlaskForm
✓ from flask_wtf import FlaskForm
✗ from flask_wtf import Form
`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.
StringField
✓ from wtforms import StringField
✗ from flask_wtf import StringField
Since version 0.9.0, fields (e.g., `StringField`, `PasswordField`) must be imported directly from `wtforms`, not `flask_wtf`.
DataRequired
✓ from wtforms.validators import DataRequired
Validators are imported directly from `wtforms.validators`.
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)
Debug
Known issues
breakingThe base form class `flask_wtf.Form` was deprecated and removed. Applications should now import and inherit from `flask_wtf.FlaskForm`.fixChange `from flask_wtf import Form` to `from flask_wtf import FlaskForm`.
affects: >=1.0.0 (Introduced in 0.9.0 with `FlaskForm`, `Form` removed in 1.0.0)
breakingWTForms fields (e.g., `StringField`, `IntegerField`) are no longer imported from `flask_wtf`.fixImport fields directly from `wtforms` (e.g., `from wtforms import StringField`).
affects: >=0.9.0
gotchaCSRF protection requires `app.config['SECRET_KEY']` to be set to a strong, random string. Without it, Flask-WTF's CSRF features will not work, or forms will fail validation.fixSet a secure `SECRET_KEY` in your Flask app's configuration, ideally loaded from an environment variable: `app.config['SECRET_KEY'] = os.environ.get('FLASK_SECRET_KEY', 'fallback-secret-key')`. affects: All versions
gotchaFor forms that include `FileField` for file uploads, the HTML form tag must include `enctype="multipart/form-data"` to ensure file data is correctly sent to the server.fixAdd `enctype="multipart/form-data"` to your HTML `<form>` tag when handling file uploads.
affects: All versions
gotchaThe `form.validate_on_submit()` method only validates if the request method is POST, PUT, PATCH, or DELETE. It will always return `False` for GET requests, which can be a common source of confusion.fixUse `form.validate_on_submit()` for handling form submissions (typically POST). For displaying an initial form (GET), simply create the form instance without calling `validate_on_submit()`.
affects: All versions
breakingThe application experienced a timeout during execution. While this is a general execution issue rather than a specific API misuse, it can sometimes be triggered by complex or misconfigured form processing, custom validators, or template rendering involving Flask-WTF. Reviewing related logic for infinite loops or resource-intensive operations is crucial.fixExamine the application's logs for any errors or warnings that precede the timeout. Use a debugger or profiling tools to identify bottlenecks or infinite loops in form validation, processing, or rendering paths. Ensure custom validators and data handling routines are efficient and terminate correctly.
affects: All versions
Errors
Common errors & fixes
400 Bad Request: The CSRF token is missing
The Flask application's `SECRET_KEY` is not set, the CSRF token is not rendered in the HTML form, or the session holding the token is not persisting correctly.
fixSet a strong `app.secret_key` in your Flask configuration. In your HTML form, ensure you render the CSRF token using `{{ form.csrf_token }}` or `{{ form.hidden_tag() }}` inside the `<form>` tags. ModuleNotFoundError: No module named 'flask_wtf'
The `flask-wtf` library is either not installed in your current Python environment or the environment where it's installed is not active.
fixInstall the library using pip: `pip install flask-wtf`. If using a virtual environment, ensure it is activated before running your application.
jinja2.exceptions.UndefinedError: 'form' object has no attribute 'csrf_token'
This error typically occurs when a form object passed to a Jinja2 template is not an instance of `flask_wtf.FlaskForm`, or when CSRF protection is disabled (e.g., `WTF_CSRF_ENABLED = False`) but the template still attempts to access `form.csrf_token` without checking its existence.
fixEnsure your form class inherits from `flask_wtf.FlaskForm`. If intentionally disabling CSRF (e.g., during testing), update your template to conditionally render the token: `{% if form.csrf_token %}{{ form.csrf_token }}{% endif %}`. `form.validate_on_submit()` always returns False
This usually indicates that the form submission failed validation, most commonly due to a missing or invalid CSRF token, or other client-side input validation failures that are not immediately apparent.
fixFirst, ensure `app.secret_key` is configured and `{{ form.csrf_token }}` or `{{ form.hidden_tag() }}` is present in your HTML form. To debug other validation issues, print `form.errors` after `form.validate_on_submit()` to see specific field validation messages. Upgrade
Version history
1.3.0latest on PyPI · released Apr 23, 2026
Audit
Dependencies
FlaskrequiredCore web framework integration.
WTFormsrequiredUnderlying form validation and rendering library.
itsdangerousrequiredUsed for secure token signing, including CSRF tokens.
email-validatoroptionalProvides an Email validator, required for 'email' extra.