Flask-WTF Documentation

repository·main·Indexed 23 days ago

https://github.com/pallets-eco/flask-wtf

A Flask extension that integrates WTForms to provide form rendering, validation, and CSRF protection. It includes specialized support for CSRFProtect, file uploads via FileField and MultipleFileField, and bot protection using RecaptchaField.

Tokens
7.6K
Snippets
18
Records
56
Agent score
80%

What's inside Flask-WTF

  1. Overview of Flask-WTF features

    main

    Flask-WTF provides a simple integration between Flask and WTForms. It is designed to handle common web form requirements securely and efficiently. Key features include:

    • WTForms Integration: Uses WTForms for form definition and validation.
    • CSRF Protection: Provides both secure forms with individual CSRF tokens and global CSRF protection for the entire application.
    • reCAPTCHA Support: Built-in support for Google reCAPTCHA.
    • File Uploads: Supports file uploads that work with Flask-Uploads.
    • Internationalization: Integrates with Flask-Babel for multi-language support.
  2. Overview of Flask-WTF

    main

    Flask-WTF provides a simple integration between the Flask web framework and WTForms. It extends WTForms functionality to include essential web security and utility features specifically for Flask applications, such as:

    • CSRF Protection: Cross-Site Request Forgery protection.
    • File Uploads: Simplified handling of file uploads within forms.
    • reCAPTCHA: Integration for Google reCAPTCHA validation.
  3. Render CSRF tokens and hidden fields in templates

    main

    Because FlaskForm automatically includes a CSRF token, you must render it in your HTML templates to prevent CSRF attacks.

    • To render only the CSRF token, use {{ form.csrf_token }}.
    • To render all hidden fields at once (including the CSRF token and any other hidden fields defined in your form), use {{ form.hidden_tag() }}.
    {# Rendering only the CSRF token #}
    <form method="POST" action="/">
        {{ form.csrf_token }}
        {{ form.name.label }} {{ form.name(size=20) }}
        <input type="submit" value="Go">
    </form>
    
    {# Rendering all hidden fields at once #}
    <form method="POST" action="/">
        {{ form.hidden_tag() }}
        {{ form.name.label }} {{ form.name(size=20) }}
        <input type="submit" value="Go">
    </form>
  4. Validate forms in view handlers

    main

    In your Flask route, instantiate the form class. You do not need to manually pass request.form to the form; Flask-WTF loads it automatically. Use the validate_on_submit() method to check if the request is a POST request and if the form data passes all defined validators.

    @app.route('/submit', methods=['GET', 'POST'])
    def submit():
        form = MyForm()
        if form.validate_on_submit():
            return redirect('/success')
        return render_template('submit.html', form=form)
  5. Exclude views from CSRF protection

    main

    While all views should be protected, you can selectively exclude specific views or entire blueprints using the @csrf.exempt decorator or the exempt() method.

    To disable protection globally and manually trigger it (e.g., in a before_request hook), set WTF_CSRF_CHECK_DEFAULT to False. When calling csrf.protect() manually, pass apply_exemptions=True to ensure that views marked with @csrf.exempt are still skipped.

    # Exclude a single view
    @app.route('/foo', methods=('GET', 'POST'))
    @csrf.exempt
    def my_handler():
        return 'ok'
    
    # Exclude an entire blueprint
    csrf.exempt(account_blueprint)
    
    # Manual protection with exemptions support
    @app.before_request
    def check_csrf():
        if not is_oauth(request):
            # apply_exemptions=True ensures @csrf.exempt still works
            csrf.protect(apply_exemptions=True)
  6. Enable CSRF protection globally

    main

    To protect views that do not use FlaskForm or to protect AJAX requests, register the CSRFProtect extension. You can initialize it immediately or lazily using init_app.

    CSRF protection requires a secret key. It defaults to the Flask app's SECRET_KEY, but you can specify a dedicated key using the WTF_CSRF_SECRET_KEY configuration setting.

    Warning: Ensure your webserver cache policy does not cache pages longer than the WTF_CSRF_TIME_LIMIT value, as this can lead to expired CSRF token errors.

    from flask_wtf.csrf import CSRFProtect
    
    # Immediate initialization
    csrf = CSRFProtect(app)
    
    # Lazy initialization
    csrf = CSRFProtect()
    
    def create_app():
        app = Flask(__name__)
        csrf.init_app(app)
  7. Display form validation errors in templates

    main

    When a form fails validation, error messages are attached to the specific field. You can iterate over form.field_name.errors in your Jinja template to display these messages to the user.

    {% if form.name.errors %}
        <ul class="errors">
        {% for error in form.name.errors %}
            <li>{{ error }}</li>
        {% endfor %}
        </ul>
    {% endif %}
  8. Send CSRF tokens in JavaScript AJAX requests

    main

    When making AJAX requests, read the token from the meta tag (rendered via csrf_meta_tag()) and include it in the X-CSRFToken header.

    // Using fetch
    const token = document.querySelector('meta[name="csrf-token"]').content;
    
    fetch("/api/resource", {
        method: "POST",
        headers: { "X-CSRFToken": token, "Content-Type": "application/json" },
        body: JSON.stringify(data),
    });
    
    // Using Axios (configure once at startup)
    axios.defaults.headers.common["X-CSRFToken"] =
        document.querySelector('meta[name="csrf-token"]').content;
  9. Create forms with FlaskForm

    main

    To define a form in Flask-WTF, create a class that inherits from FlaskForm. You must import fields (like StringField) and validators (like DataRequired) directly from the wtforms package, as Flask-WTF no longer re-exports them. Each FlaskForm automatically includes a CSRF token hidden field for security.

    from flask_wtf import FlaskForm
    from wtforms import StringField
    from wtforms.validators import DataRequired
    
    class MyForm(FlaskForm):
        name = StringField('name', validators=[DataRequired()])