FLASK-WTF(1) Flask-WTF FLASK-WTF(1)

flask-wtf - Flask-WTF 1.2.1 [image: Flask-WTF] [image]

Simple integration of Flask and WTForms, including CSRF, file upload, and reCAPTCHA.

  • Integration with WTForms.
  • Secure Form with CSRF token.
  • Global CSRF protection.
  • reCAPTCHA support.
  • File upload that works with Flask-Uploads.
  • Internationalization using Flask-Babel.

This part of the documentation, which is mostly prose, begins with some background information about Flask-WTF, then focuses on step-by-step instructions for getting the most out of Flask-WTF.

The Python Packaging Guide contains general information about how to manage your project and dependencies.

Install or upgrade using pip.

pip install -U Flask-WTF

The latest code is available from GitHub. Clone the repository then install using pip.

git clone https://github.com/wtforms/flask-wtf
pip install -e ./flask-wtf

Or install the latest build from an archive.

pip install -U https://github.com/wtforms/flask-wtf/archive/main.tar.gz

Eager to get started? This page gives a good introduction to Flask-WTF. It assumes you already have Flask-WTF installed. If you do not, head over to the Installation section.

Creating Forms

Flask-WTF provides your Flask application integration with WTForms. For example:

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

NOTE:

From version 0.9.0, Flask-WTF will not import anything from wtforms, you need to import fields from wtforms.

In addition, a CSRF token hidden field is created automatically. You can render this in your template:

<form method="POST" action="/">
    {{ form.csrf_token }}
    {{ form.name.label }} {{ form.name(size=20) }}
    <input type="submit" value="Go">
</form>

If your form has multiple hidden fields, you can render them in one block using hidden_tag().

<form method="POST" action="/">
    {{ form.hidden_tag() }}
    {{ form.name.label }} {{ form.name(size=20) }}
    <input type="submit" value="Go">
</form>

Validating the request in your view handlers:

@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)

Note that you don't have to pass request.form to Flask-WTF; it will load automatically. And the convenient validate_on_submit will check if it is a POST request and if it is valid.

If your forms include validation, you'll need to add to your template to display any error messages. Using the form.name field from the example above, that would look like this:

{% if form.name.errors %}
    <ul class="errors">
    {% for error in form.name.errors %}
        <li>{{ error }}</li>
    {% endfor %}
    </ul>
{% endif %}

Heading over to Creating Forms to learn more skills.

Creating Forms

Without any configuration, the FlaskForm will be a session secure form with csrf protection. We encourage you not to change this.

But if you want to disable the csrf protection, you can pass:

form = FlaskForm(meta={'csrf': False})

You can disable it globally—though you really shouldn't—with the configuration:

WTF_CSRF_ENABLED = False

In order to generate the csrf token, you must have a secret key, this is usually the same as your Flask app secret key. If you want to use another secret key, config it:

WTF_CSRF_SECRET_KEY = 'a random string'

The FileField provided by Flask-WTF differs from the WTForms-provided field. It will check that the file is a non-empty instance of FileStorage, otherwise data will be None.

from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileRequired
from werkzeug.utils import secure_filename
class PhotoForm(FlaskForm):
    photo = FileField(validators=[FileRequired()])
@app.route('/upload', methods=['GET', 'POST'])
def upload():
    form = PhotoForm()
    if form.validate_on_submit():
        f = form.photo.data
        filename = secure_filename(f.filename)
        f.save(os.path.join(
            app.instance_path, 'photos', filename
        ))
        return redirect(url_for('index'))
    return render_template('upload.html', form=form)

Similarly, you can use the MultipleFileField provided by Flask-WTF to handle multiple files. It will check that the files is a list of non-empty instance of FileStorage, otherwise data will be None.

from flask_wtf import FlaskForm
from flask_wtf.file import MultipleFileField, FileRequired
from werkzeug.utils import secure_filename
class PhotoForm(FlaskForm):
    photos = MultipleFileField(validators=[FileRequired()])
@app.route('/upload', methods=['GET', 'POST'])
def upload():
    form = PhotoForm()
    if form.validate_on_submit():
        for f in form.photo.data:  # form.photo.data return a list of FileStorage object
            filename = secure_filename(f.filename)
            f.save(os.path.join(
                app.instance_path, 'photos', filename
            ))
        return redirect(url_for('index'))
    return render_template('upload.html', form=form)

Remember to set the enctype of the HTML form to multipart/form-data, otherwise request.files will be empty.

<form method="POST" enctype="multipart/form-data">
    ...
</form>

Flask-WTF handles passing form data to the form for you. If you pass in the data explicitly, remember that request.form must be combined with request.files for the form to see the file data.

form = PhotoForm()
# is equivalent to:
from flask import request
from werkzeug.datastructures import CombinedMultiDict
form = PhotoForm(CombinedMultiDict((request.files, request.form)))

Flask-WTF supports validating file uploads with FileRequired, FileAllowed, and FileSize. They can be used with both Flask-WTF's and WTForms's FileField and MultipleFileField classes.

FileAllowed works well with Flask-Uploads.

from flask_uploads import UploadSet, IMAGES
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed, FileRequired
images = UploadSet('images', IMAGES)
class UploadForm(FlaskForm):
    upload = FileField('image', validators=[
        FileRequired(),
        FileAllowed(images, 'Images only!')
    ])

It can be used without Flask-Uploads by passing the extensions directly.

class UploadForm(FlaskForm):
    upload = FileField('image', validators=[
        FileRequired(),
        FileAllowed(['jpg', 'png'], 'Images only!')
    ])

Flask-WTF also provides Recaptcha support through a RecaptchaField:

from flask_wtf import FlaskForm, RecaptchaField
from wtforms import TextField
class SignupForm(FlaskForm):
    username = TextField('Username')
    recaptcha = RecaptchaField()

This comes with a number of configuration variables, some of which you have to configure.

RECAPTCHA_PUBLIC_KEY required A public key.
RECAPTCHA_PRIVATE_KEY required A private key.
RECAPTCHA_API_SERVER optional Specify your Recaptcha API server.
RECAPTCHA_PARAMETERS optional A dict of JavaScript (api.js) parameters.
RECAPTCHA_DATA_ATTRS optional A dict of data attributes options. https://developers.google.com/recaptcha/docs/display#javascript_resource_apijs_parameters

Example of RECAPTCHA_PARAMETERS, and RECAPTCHA_DATA_ATTRS:

RECAPTCHA_PARAMETERS = {'hl': 'zh', 'render': 'explicit'}
RECAPTCHA_DATA_ATTRS = {'theme': 'dark'}

For your convenience, when testing your application, if app.testing is True, the recaptcha field will always be valid.

And it can be easily setup in the templates:

<form action="/" method="post">
    {{ form.username }}
    {{ form.recaptcha }}
</form>

We have an example for you: recaptcha@github.

CSRF Protection

Any view using FlaskForm to process the request is already getting CSRF protection. If you have views that don't use FlaskForm or make AJAX requests, use the provided CSRF extension to protect those requests as well.

To enable CSRF protection globally for a Flask app, register the CSRFProtect extension.

from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect(app)

Like other Flask extensions, you can apply it lazily:

csrf = CSRFProtect()
def create_app():
    app = Flask(__name__)
    csrf.init_app(app)

NOTE:

CSRF protection requires a secret key to securely sign the token. By default this will use the Flask app's SECRET_KEY. If you'd like to use a separate token you can set WTF_CSRF_SECRET_KEY.

When using a FlaskForm, render the form's CSRF field like normal.

<form method="post">
    {{ form.csrf_token }}
</form>

If the template doesn't use a FlaskForm, render a hidden input with the token in the form.

<form method="post">
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
</form>

When sending an AJAX request, add the X-CSRFToken header to it. For example, in jQuery you can configure all requests to send the token.

<script type="text/javascript">
    var csrf_token = "{{ csrf_token() }}";
    $.ajaxSetup({
        beforeSend: function(xhr, settings) {
            if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
                xhr.setRequestHeader("X-CSRFToken", csrf_token);
            }
        }
    });
</script>

In Axios you can set the header for all requests with axios.defaults.headers.common.

<script type="text/javascript">
    axios.defaults.headers.common["X-CSRFToken"] = "{{ csrf_token() }}";
</script>

When CSRF validation fails, it will raise a CSRFError. By default this returns a response with the failure reason and a 400 code. You can customize the error response using Flask's errorhandler().

from flask_wtf.csrf import CSRFError
@app.errorhandler(CSRFError)
def handle_csrf_error(e):
    return render_template('csrf_error.html', reason=e.description), 400

We strongly suggest that you protect all your views with CSRF. But if needed, you can exclude some views using a decorator.

@app.route('/foo', methods=('GET', 'POST'))
@csrf.exempt
def my_handler():
    # ...
    return 'ok'

You can exclude all the views of a blueprint.

csrf.exempt(account_blueprint)

You can disable CSRF protection in all views by default, by setting WTF_CSRF_CHECK_DEFAULT to False, and selectively call protect() only when you need. This also enables you to do some pre-processing on the requests before checking for the CSRF token.

@app.before_request
def check_csrf():
    if not is_oauth(request):
        csrf.protect()

WTF_CSRF_ENABLED Set to False to disable all CSRF protection. Default is True.
WTF_CSRF_CHECK_DEFAULT When using the CSRF protection extension, this controls whether every view is protected by default. Default is True.
WTF_CSRF_SECRET_KEY Random data for generating secure tokens. If this is not set then SECRET_KEY is used.
WTF_CSRF_METHODS HTTP methods to protect from CSRF. Default is {'POST', 'PUT', 'PATCH', 'DELETE'}.
WTF_CSRF_FIELD_NAME Name of the form field and session key that holds the CSRF token. Default is csrf_token.
WTF_CSRF_HEADERS HTTP headers to search for CSRF token when it is not provided in the form. Default is ['X-CSRFToken', 'X-CSRF-Token'].
WTF_CSRF_TIME_LIMIT Max age in seconds for CSRF tokens. Default is 3600. If set to None, the CSRF token is valid for the life of the session.
WTF_CSRF_SSL_STRICT Whether to enforce the same origin policy by checking that the referrer matches the host. Only applies to HTTPS requests. Default is True.
WTF_I18N_ENABLED Set to False to disable Flask-Babel I18N support. Also set to False if you want to use WTForms's built-in messages directly, see more info here. Default is True.

RECAPTCHA_PUBLIC_KEY required A public key.
RECAPTCHA_PRIVATE_KEY required A private key. https://www.google.com/recaptcha/admin
RECAPTCHA_PARAMETERS optional A dict of configuration options.
RECAPTCHA_HTML optional Override default HTML template for Recaptcha.
RECAPTCHA_DATA_ATTRS optional A dict of data- attrs to use for Recaptcha div
RECAPTCHA_SCRIPT optional Override the default captcha script URI in case an alternative service to reCAPtCHA, e.g. hCaptcha is used. Default is 'https://www.google.com/recaptcha/api.js'
RECAPTCHA_DIV_CLASS optional Override the default class of the captcha div in case an alternative captcha service is used. Default is 'g-recaptcha'
RECAPTCHA_VERIFY_SERVER optional Override the default verification server in case an alternative service is used. Default is 'https://www.google.com/recaptcha/api/siteverify'

CSRF errors are logged at the INFO level to the flask_wtf.csrf logger. You still need to configure logging in your application in order to see these messages.

If you are looking for information on a specific function, class or method, this part of the documentation is for you.

CSRF Protection

Legal information and changelog are here.

Copyright 2010 WTForms

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1.
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3.
Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Released 2023-10-02

Fix a bug introduced with #556 where file validators were editing the file fields content. #578

Released 2023-10-01

Add field MultipleFileField. FileRequired, FileAllowed, FileSize now can be used to validate multiple files #556 #338

Released 2023-09-29

  • Fixed Flask 2.3 deprecations of werkzeug.urls.url_encode and flask.Markup #565 #561
  • Stop support for python 3.7 #574
  • Use pyproject.toml instead of setup.cfg #576
  • Fixed nested blueprint CSRF exemption #572

Released 2023-01-17

Fixed validate extra_validators parameter. #548

Released 2023-01-15

  • Drop support for Python 3.6.
  • validate_on_submit takes a extra_validators parameters #479
  • Stop supporting Flask-Babelex #540
  • Support for python 3.11 #542
  • Remove unused call to JSONEncoder #536

Released 2022-03-31

Update compatibility with the latest Werkzeug release. #511

Released 2021-11-07

  • Deprecated items removal #484
  • Support for alternatives captcha services #425 #342 #387 #384

Released 2021-05-25

Add python_requires metadata to avoid installing on unsupported Python versions. #442

Released 2021-05-24

  • Drop support for Python < 3.6. #416
  • FileSize validator. #307#365
  • Extra requirement email installs the email_validator package. #423
  • Fixed Flask 2.0 warnings. #434
  • Various documentation fixes. #315#321#335#344#386#400, #404#420#437
  • Various CI fixes. #405#438

Released 2020-02-06

Fix deprecated imports from werkzeug and collections.

Released 2017-01-10

Fix bug where FlaskForm assumed meta argument was not None if it was passed. #278

Released 2017-01-10

Fix bug where the file validators would incorrectly identify an empty file as valid data. #276, #277
  • FileField is no longer deprecated. The data is checked during processing and only set if it's a valid file.
  • has_file is deprecated; it's now equivalent to bool(field.data).
  • FileRequired and FileAllowed work with both the Flask-WTF and WTForms FileField classes.
  • The Optional validator now works with FileField.

Released 2017-01-06

Use ItsDangerous to sign CSRF tokens and check expiration instead of doing it ourselves. #264
  • All tokens are URL safe, removing the url_safe parameter from generate_csrf. #206
  • All tokens store a timestamp, which is checked in validate_csrf. The time_limit parameter of generate_csrf is removed.
  • Remove the app attribute from CsrfProtect, use current_app. #264
  • CsrfProtect protects the DELETE method by default. #264
  • The same CSRF token is generated for the lifetime of a request. It is exposed as g.csrf_token for use during testing. #227#264
  • CsrfProtect.error_handler is deprecated. #264
  • Handlers that return a response work in addition to those that raise an error. The behavior was not clear in previous docs.
  • #200#209#243#252
Use Form.Meta instead of deprecated SecureForm for CSRF (and everything else). #216#271
csrf_enabled parameter is still recognized but deprecated. All other attributes and methods from SecureForm are removed. #271
  • Provide WTF_CSRF_FIELD_NAME to configure the name of the CSRF token. #271
  • validate_csrf raises wtforms.ValidationError with specific messages instead of returning True or False. This breaks anything that was calling the method directly. #239#271
CSRF errors are logged as well as raised. #239
  • CsrfProtect is renamed to CSRFProtect. A deprecation warning is issued when using the old name. CsrfError is renamed to CSRFError without deprecation. #271
  • FileField is deprecated because it no longer provides functionality over the provided validators. Use wtforms.FileField directly. #272

Released 2016-10-6

  • Deprecation warning for Form is shown during __init__ instead of immediately when subclassing. #262
  • Don't use pkg_resources to get version, for compatibility with GAE. #261

Released 2016-09-29

  • Form is renamed to FlaskForm in order to avoid name collision with WTForms's base class. Using Form will show a deprecation warning. #250
  • hidden_tag no longer wraps the hidden inputs in a hidden div. This is valid HTML5 and any modern HTML parser will behave correctly. #193#217
  • flask_wtf.html5 is deprecated. Import directly from wtforms.fields.html5. #251
  • is_submitted is true for PATCH and DELETE in addition to POST and PUT. #187
  • generate_csrf takes a token_key parameter to specify the key stored in the session. #206
  • generate_csrf takes a url_safe parameter to allow the token to be used in URLs. #206
  • form.data can be accessed multiple times without raising an exception. #248
  • File extension with multiple parts (.tar.gz) can be used in the FileAllowed validator. #201

Released 2015-07-09

  • Abstract protect_csrf() into a separate method.
  • Update reCAPTCHA configuration.
  • Fix reCAPTCHA error handle.

Released 2015-01-21

Use the new reCAPTCHA API. #164

Released 2014-11-16

  • Add configuration: WTF_CSRF_HEADERS. #159
  • Support customize hidden tags. #150
  • And many more bug fixes.

Released 2014-09-03

Update translation for reCaptcha. #146

Released 2014-08-26

  • Update RECAPTCHA_API_SERVER_URL. #145
  • Update requirement Werkzeug >= 0.9.5.
  • Fix CsrfProtect exempt for blueprints. #143

Released 2014-07-16

  • Add configuration: WTF_CSRF_METHODS.
  • Support WTForms 2.0 now.
  • Fix CSRF validation without time limit (time_limit=False).
  • csrf_exempt supports blueprint. #111

Released 2014-03-21

  • csrf_token for all template types. #112
  • Make FileRequired a subclass of InputRequired. #108

Released 2013-12-20

  • Bugfix for csrf module when form has a prefix.
  • Compatible support for WTForms 2.
  • Remove file API for FileField

Released 2013-10-02

  • Fix validation of recaptcha when app in testing mode. #89
  • Bugfix for csrf module. #91

Released 2013-09-11

  • Upgrade WTForms to 1.0.5.
  • No lazy string for i18n. #77
  • No DateInput widget in HTML5. #81
  • PUT and PATCH for CSRF. #86

Released 2013-08-21

Compatibility with Flask < 0.10. #82

Released 2013-08-15

  • Add i18n support. #65
  • Use default HTML5 widgets and fields provided by WTForms.
  • Python 3.3+ support.
  • Redesign form, replace SessionSecureForm.
  • CSRF protection solution.
  • Drop WTForms imports.
  • Fix recaptcha i18n support.
  • Fix recaptcha validator for Python 3.
  • More test cases, it's 90%+ coverage now.
  • Redesign documentation.

Released 2013-03-28

  • Recaptcha Validator now returns provided message. #66
  • Minor doc fixes.
  • Fixed issue with tests barking because of nose/multiprocessing issue.

Released 2013-03-13

  • Update documentation to indicate pending deprecation of WTForms namespace facade.
  • PEP8 fixes. #64
  • Fix Recaptcha widget. #49

Initial development by Dan Jacob and Ron Duplain.

Thank you for considering contributing to Flask-WTF!

Please don't use the issue tracker for this. The issue tracker is a tool to address bugs and feature requests in Flask-WTF itself. Use one of the following resources for questions about using Flask-WTF or issues with your own code:

  • The #get-help channel on our Discord chat: https://discord.gg/pallets
  • The mailing list flask@python.org for long term discussion or larger issues.
  • Ask on Stack Overflow. Search with Google first using: site:stackoverflow.com flask-wtf {search term, exception message, etc.}

Include the following information in your post:

  • Describe what you expected to happen.
  • If possible, include a minimal reproducible example to help us identify the issue. This also helps check that the issue is not with your own code.
  • Describe what actually happened. Include the full traceback if there was an exception.
  • List your Python, Flask-WTF, and WTForms versions. If possible, check if this issue is already fixed in the latest releases or the latest code in the repository.

If there is not an open issue for what you want to submit, prefer opening one for discussion before working on a PR. You can work on any issue that doesn't have an open PR linked to it or a maintainer assigned to it. These show up in the sidebar. No need to ask if you can work on an issue that interests you.

Include the following in your patch:

  • Use Black to format your code. This and other tools will run automatically if you install pre-commit using the instructions below.
  • Include tests if your patch adds or changes code. Make sure the test fails without your patch.
  • Update any relevant docs pages and docstrings. Docs pages and docstrings should be wrapped at 72 characters.
  • Add an entry in CHANGES.rst. Use the same style as other entries. Also include .. versionchanged:: inline changelogs in relevant docstrings.

  • Download and install the latest version of git.
  • Configure git with your username and email.
$ git config --global user.name 'your name'
$ git config --global user.email 'your email'
  • Make sure you have a GitHub account.
  • Fork Flask-WTF to your GitHub account by clicking the Fork button.
  • Clone the main repository locally.
$ git clone https://github.com/wtforms/flask-wtf
$ cd flask-wtf
Add your fork as a remote to push your work to. Replace {username} with your username. This names the remote "fork", the default WTForms remote is "origin".
$ git remote add fork https://github.com/{username}/flask-wtf
Create a virtualenv.
$ python3 -m venv env
$ . env/bin/activate

On Windows, activating is different.

> env\Scripts\activate
Upgrade pip and setuptools.
$ python -m pip install --upgrade pip setuptools
Install the development dependencies, then install Flask-WTF in editable mode.
$ pip install -r requirements/dev.txt && pip install -e .
Install the pre-commit hooks.
$ pre-commit install

Create a branch to identify the issue you would like to work on. If you're submitting a bug or documentation fix, branch off of the latest ".x" branch.
$ git fetch origin
$ git checkout -b your-branch-name origin/1.0.x

If you're submitting a feature addition or change, branch off of the "main" branch.

$ git fetch origin
$ git checkout -b your-branch-name origin/main
  • Using your favorite editor, make your changes, committing as you go.
  • Include tests that cover any code changes you make. Make sure the test fails without your patch. Run the tests as described below.
  • Push your commits to your fork on GitHub and create a pull request. Link to the issue being addressed with fixes #123 in the pull request.
$ git push --set-upstream fork your-branch-name

Run the basic test suite with pytest.

$ pytest

This runs the tests for the current environment, which is usually sufficient. CI will run the full suite when you submit your pull request. You can run the full test suite with tox if you don't want to wait.

$ tox

Generating a report of lines that do not have test coverage can indicate where to start contributing. Run pytest using coverage and generate a report.

$ pip install coverage
$ coverage run -m pytest
$ coverage html

Open htmlcov/index.html in your browser to explore the report.

Read more about coverage.

Build the docs in the docs directory using Sphinx.

$ cd docs
$ make html

Open _build/html/index.html in your browser to view the docs.

Read more about Sphinx.

WTForms

2024 WTForms

April 8, 2024 1.2.x