Flask - WTF Extension

Last Updated : 11 Jun, 2026

Flask-WTF is a Flask extension that integrates the WTForms library, making form creation and validation easier in Flask applications. It provides a structured way to build forms, handle validation, and render them in HTML. In this article, we'll explore how Flask-WTF works by building a Signup form. Before diving in, let's go over its features, field types, and prerequisites.

  • Secure Form Handling: Automatically manages CSRF protection to prevent unauthorized submissions.
  • Easy Form Rendering: Supports various field types like text fields, checkboxes, and dropdowns for smooth HTML integration.
  • Built-in Validation: Includes required fields, length constraints, pattern matching, and support for custom validation.
  • File Uploads: Allows users to upload files through forms seamlessly.

Prerequisites: Knowledge of Python, HTML, CSS, Javascript

Installation

To use Flask-WTF, we first need to install it using pip and import it into our application.

pip install flask-wtf

In Flask-WTF, forms are defined as classes that extend the FlaskForm class. Fields are declared as class variables, making form creation simple and structured.

Common WTForms Field Types:

  • StringField: Text input field for string data.
  • PasswordField: Input field for password values.
  • BooleanField: Checkbox for True/False selection.
  • DecimalField: Input field for decimal values.
  • RadioField: Group of radio buttons for single selection.
  • SelectField: Dropdown list for single selection.
  • TextAreaField: Multi-line text input field.
  • FileField: File upload field.

Custom Validators in Flask-WTF

Flask-WTF provides built-in validators for common validation tasks, but custom validators can be created when application-specific validation rules are required. Custom validators help enforce additional constraints beyond the standard validation options.

Example: The following validator ensures that a username contains only alphabetic characters:

Python
from wtforms import StringField
from wtforms.validators import ValidationError

def validate_username(form, field):
    if not field.data.isalpha():
        raise ValidationError(
            'Username must contain only letters.'
        )

class SignupForm(FlaskForm):
    username = StringField(
        'Username',
        validators=[validate_username]
    )

Explanation:

  • Create a Validation Function: The custom validator receives the form and field objects as arguments.
  • Apply Validation Logic: The function checks whether the submitted value satisfies the required condition.
  • Raise ValidationError: If validation fails, a ValidationError is raised with an appropriate error message.
  • Attach Validator to a Field: The custom validator is added to the field's validators list and is executed during form validation.

Flask - WTF Examples

Example 1

Building a sample form using the above field types

Python
# Importing Libraries..
from flask import Flask, render_template, request
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField
from wtforms import DecimalField, RadioField, SelectField, TextAreaField, FileField
from wtforms.validators import InputRequired
from werkzeug.security import generate_password_hash

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secretkey'


class MyForm(FlaskForm):
    name = StringField('Name', validators=[InputRequired()])
    password = PasswordField('Password', validators=[InputRequired()])
    remember_me = BooleanField('Remember me')
    salary = DecimalField('Salary', validators=[InputRequired()])
    gender = RadioField('Gender', choices=[
                        ('male', 'Male'), ('female', 'Female')])
    country = SelectField('Country', choices=[('IN', 'India'), ('US', 'United States'),
                                              ('UK', 'United Kingdom')])
    message = TextAreaField('Message', validators=[InputRequired()])
    photo = FileField('Photo')


@app.route('/', methods=['GET', 'POST'])
def index():
    form = MyForm()
    if form.validate_on_submit():
        name = form.name.data
        password = form.password.data
        remember_me = form.remember_me.data
        salary = form.salary.data
        gender = form.gender.data
        country = form.country.data
        message = form.message.data
        photo = (
           form.photo.data.filename
           if form.photo.data
           else "No file uploaded"
        )
        return (
           f'Name: {name} <br>'
           f'Password: {generate_password_hash(password)} <br>'
           f'Remember me: {remember_me} <br>'
           f'Salary: {salary} <br>'
           f'Gender: {gender} <br>'
           f'Country: {country} <br>'
           f'Message: {message} <br>'
           f'Photo: {photo}'
        )
    return render_template('index.html', form=form)


if __name__ == '__main__':
    app.run()

index.html

HTML
<!DOCTYPE html>
<html>
<head>
    <title>My Form</title>
</head>
<body>
    <h1>My Form</h1>
    <form method="post" action="/" enctype="multipart/form-data">
        {{ form.csrf_token }}
        <p>{{ form.name.label }} {{ form.name() }}</p>
        <p>{{ form.password.label }} {{ form.password() }}</p>
        <p>{{ form.remember_me() }} {{ form.remember_me.label }}</p>
        <p>{{ form.salary.label }} {{ form.salary() }}</p>
        <p>{{ form.gender.label }} {{ form.gender() }}</p>
        <p>{{ form.country.label }} {{ form.country() }}</p>
        <p>{{ form.message.label }} {{ form.message() }}</p>
        <p>{{ form.photo.label }} {{ form.photo() }}</p>
        <p><input type="submit" value="Submit"></p>
    </form>
</body>
</html>

Explanation:

  • Created a Form Class: MyForm inherits from FlaskForm and includes various fields like StringField, PasswordField, BooleanField, etc., with appropriate validators.
  • Passed Form to Template: In the index function, an instance of MyForm is created and sent to index.html.
  • Handled Form Submission: The validate_on_submit method checks if the form is valid before processing data.
  • Retrieved and Displayed Data: If valid, form data is extracted and shown in the browser.
  • Secured Passwords: Used generate_password_hash to encrypt passwords for better security.

Output:

Flask-Forms
Form details
submitted-forms
Details of the submitted form

Example 2

Let's see another example of a simple Sign-up form using the Flask-WTF library where we use the field types mentioned above in our application.

Python
from flask import Flask, render_template
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField ,SubmitField
from wtforms.validators import InputRequired, Length

app = Flask(__name__)

app.config['SECRET_KEY'] = 'secret'

# Using StringField, PasswordField andSubmitField from Flask-WTForms library 
# for username,password fields and submit button..  
class LoginForm(FlaskForm):
    username = StringField('Username', validators=[InputRequired('Username required!'), 
               Length(min=5, max=25, message='Username must be in 5 to 25 characters')])
    password = PasswordField('Password',validators=[InputRequired('Password required')])
    submit = SubmitField('Submit')
    
@app.route('/Signup', methods=['GET', 'POST'])
def form():
    form = LoginForm()
    if form.validate_on_submit():
        return '<h1>Hi {}!!. Your form is submitted successfully!!'.format(form.username.data)
    return render_template('Signup.html', form=form)

if __name__ == '__main__':
    app.run(debug=True)

Signup.html code

HTML
<!DOCTYPE html>
<html>
<head>
<title>Flask Form</title>
</head>
<body>
<h1>Signup </h1>
<form method="POST" action="{{ url_for('form') }}">
    {{ form.hidden_tag() }}
    {{ form.username.label }}
    {{ form.username }}
    <br>
    <br>
    {{ form.password.label }}
    {{ form.password }}
    <br>
    <br>
    {{ form.submit }}
</form>
</body>
</html>

Explanation:

  • {{ form.hidden_tag() }} renders all hidden fields automatically, including the CSRF token.
  • {{ form.username.label }} renders an HTML label for the username field of the form.
  • {{ form.username }} renders an HTML input element for the username field of the form.
  • {{ form.password.label }} renders an HTML label for the password field of the form.
  • {{ form.password }} renders an HTML input element for the password field of the form.
  • {{ form. submit }} renders an HTML input element for the form's submit button.

The form is validated using validate_on_submit(). After successful validation, the submitted data is processed and a confirmation message is displayed with the username.

Output:

signup-form
Signup formĀ 
details-of-signup-form
Details of the Signup form
submitted-form
Form submitted

Common WTForms Validators

Flask-WTF provides several built-in validators that help ensure submitted data meets specific requirements before it is processed.

ValidatorPurpose
InputRequired()Ensures a value is provided
Length()Restricts input length
Email()Validates email addresses
NumberRange()Restricts numeric values
EqualTo()Compares two fields
Comment