Formularios WTForms en Flask 3

Video thumbnail

Los formularios son una parte crucial en las aplicaciones web hoy en día, es el mecanismo usado por excelencia para obtener datos de usuarios, aunque, los formularios web son netamente HTML y que pueden ser usados directamente en Flask, trae el inconveniente de que, a estos formularios hay que aplicar validaciones tanto del lado del cliente, y más importante, del servidor, para cuando se reciban los datos antes de procesarlos y guardarlos sean completamente válidos.

En mis primeros proyectos y cuando estaba comenzando con frameworks como Flask, recuerdo lo frustrante que era mantener validaciones duplicadas: un required en HTML y otro bloque de código en Python. WTForms vino a resolver justo eso, y en esta guía te contaré cómo usarlo correctamente en Flask 3, con ejemplos reales y buenas prácticas.

Otro problema consiste en que hay que crear un equivalente entre los campos de formulario con los modelos en la base de datos, es decir, usualmente estos campos de formulario tienen una relación directa o al menos bastante directa con las tablas en la base de datos, si vamos a crear un módulo para administrar publicaciones cuyos campos de formulario serían título, descripción y contenido, estos campos deben de estar presente en la tabla de la base de datos, para poder registrarlos, por lo tanto, tenemos un doble trabajo para definir estos campos en la base de datos (en el caso de Flask, a través de los modelos) y los campos de formularios.

¿Por qué los formularios son esenciales en Flask?

Un formulario no solo captura datos: también debe validarlos, protegerlos y procesarlos antes de guardarlos en la base de datos.
En Flask, podrías hacerlo con HTML puro y funciones request.form, pero pronto notarás que eso implica repetir lógica en dos sitios distintos.

Con los WTForms nos ahorramos “doble trabajo” de definir los mismos campos y validaciones en HTML y en los modelos de la base de datos. Además, la sintaxis de Python hace que todo sea mucho más limpio y fácil de mantener.

Qué es WTForms y cómo funciona con Flask 3

WTForms es una librería independiente para manejar formularios y validaciones del lado del servidor, Flask, al ser un microframewok, para extender sus funcionalidades, cuenta con paquetes como este que permiten realizar determinadas acciones.

Flask-WTF es su integración oficial con Flask, que añade soporte para plantillas Jinja2 de Flask, manejo de CSRF y helpers que simplifican todo el flujo.

Estas situaciones ocurren con cualquier tecnología del lado del servidor que en definitiva consiste en definir campos y validaciones con un mismo propósito, pero con diferente lógica en el cliente y en el servidor; es decir, que si queremos un campo para el nombre, en el cual sea requerido, en HTML sería como:

<input type="text" name="task" required>

Y en el servidor, mediante Flask sería algo como:

task = request.args.form('task')
if task is not None:
  #TO DO     

Y si en algún momento, nos solicitan cambiar la validación o agregar más, tenemos que hacer cambios en ambos lados.

Por suerte, en el caso de Flask, existe un paquete que facilita mucho el proceso y la lógica especificada, el paquete de WTForms proporciona una varios campos a los cuales les podemos aplicar validaciones en el lado del servidor, y mediante Jinja, podemos representar estos campos fácilmente para que sean convertidos a campos de formularios en HTML; estos campos son realmente propiedades que corresponden a una clase que hereda de FlaskForm a cuyos campos/propiedades, podemos definir validaciones; una clase típica de validación, luce como:

from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import InputRequired

class Task(FlaskForm):
    name = StringField('Name', validators=[InputRequired()])

Las validaciones se aplican principalmente en el lado del servidor, que es el lado más importante, aunque, dependiendo de la validación, ciertas reglas son especificadas:

<input id="name" name="name" required type="text" value="">

WTForms: Creación de formularios paso a paso

Comencemos instalando el paquete con:

$ pip install Flask-WTF 

Se instalan dos paquetes, el del plugin:

WTForms==***

Y el conector para Flask:

Flask-WTF==***

Para simplicidad, nos referiremos a WTForm como simplemente WTF o Flask WTF.

Principales campos

Tenemos distintos tipos de campos que podemos definir para crear un formulario mediante Flask WTF, que vienen siendo los siguientes:

  • fields.StringField: Representa un campo de tipo texto.
  • fields.FileField: Representa un campo de tipo archivo.
  • fields.DateField y fields.DateTimeField: Representa un campo de tipo date o datetime en Python, también, recibe un parámetro opcional llamado format que recibe el formato en String para la fecha por defecto es (format='%Y-%m-%d').
  • fields.IntegerField: Representa un campo de tipo entero.
  • fields.FloatField: Representa un campo de tipo flotante.
  • fields.PasswordField: Representa un campo de tipo password.
  • fields.RadioField: Representa un campo de tipo radio en HTML, como es de esperar, se especifica mediante un parámetro para seleccionar un listado de opciones; este listado puede ser una lista o tupla en Python.
  • fields.SelectField: Representa un campo de tipo selección en el cual, se especifica un parámetro llamado choices en el cual, se especifican en listado de opciones.

Puedes obtener más información sobre los campos disponibles en:

https://wtforms.readthedocs.io/en/master/fields/

Validaciones

Además de definir los campos, es posible agregar validaciones a los campos del formulario; para ello, tenemos clases predefinidas en:

from wtforms.validators import ***

Entre los principales:

  • InputRequired: Indica que el campo es requerido.
  • NumberRange: Se establece un número mínimo y máximo.
  • DataRequired: Indica que el campo es requerido.
  • Length: Se establece un número mínimo y máximo para el tamaño de un string.
  • Regexp: Evalúa una expresión regular.

Puedes obtener más información sobre las validaciones disponibles en:

https://wtforms.readthedocs.io/en/master/validators/

Y luego, desde el controlador, se verifica si el formulario es válido antes de realizar cualquier otra operación sobre el mismo, como, por ejemplo, guardar en la base de datos:

@app.route('/submit', methods=['GET', 'POST'])
def submit():
    form = MyForm()
    if form.validate_on_submit():
       # TO DO

Y desde el template, podemos mostrar los errores ocurridos por un campo en particular, por ejemplo, el llamado name:

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

O de todos los campos:

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

Validaciones personalizadas

Muchas veces es necesario agregar reglas más complejas que simplemente indicar si un campo es requerido, o por el tipo o mediante una expresión regular, si no, necesitamos hacer validaciones mediante funciones y realizar condicionales entre otros, en estos casos, podemos emplear validaciones personalizadas ya sea por campos; por ejemplo, para el campo name:

my_app\tasks\forms.py

from wtforms.validators import ValidationError
***
class Task(FlaskForm):
  ***
    def validate_name(form, field):
        if len(field.data) > 2:
            raise ValidationError('Name must be less than 2 characters')

Protección CSRF

Al momento de emplear algún formulario, veremos un error como el siguiente:

RuntimeError: A secret key is required to use CSRF.

Los formularios de WTForms emplean la protección CSRF y debemos de especificar una clase para la generación de este token, para ello, podemos usar el archivo de configuración:

my_app\config.py

class Config(object): 
    SQLALCHEMY_DATABASE_URI="mysql+pymysql://root:@localhost:3306/test_flask" 
    SECRET_KEY="SECRETKEY"
    ***

En este ejemplo, la clave secreta es SECRETKEY, pero puedes personalizarla a gusto.

El token CSRF (Cross Site Request Forgery) es una medida de seguridad automática en Flask-WTF.
Para activarlo, basta con definir SECRET_KEY en tu configuración, como mostramos antes.

En mis primeros intentos olvidé configurarlo y recibí el famoso error de “A secret key is required to use CSRF”. Desde entonces, lo considero uno de esos detalles que hay que dejar listos desde el inicio del proyecto.

Creando un formulario con WTForms para las tareas

Teniendo claro el funcionamiento de los formularios con Flask WTF, vamos a implementar un formulario para las tareas:

my_app\tasks\forms.py

from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import InputRequired

class Task(FlaskForm):
    name = StringField('Name', validators=[InputRequired()])

Como puedes apreciar en el código anterior, ya que, el campo para las tareas consiste en especificar el nombre, se usa un campo de tipo texto definido mediante la clase StringField y que sea requerido mediante InputRequired.

Desde el controlador, creamos una instancia del formulario:

my_app\tasks\controllers.py

from my_app.tasks import forms
***
@taskRoute.route('/create', methods=('GET', 'POST'))
def create():

   # form = forms.Task(csrf_enabled=False)
   form = forms.Task()
   if form.validate_on_submit():
      print("listo")
   #      return redirect('/success')
   #  return render_template('submit.html', form=form)

   # task_list.append(request.form.get('task')) #request.args.get('task')
   return render_template('dashboard/task/create.html', form=form)

Buenas prácticas y errores comunes

  • ✅ Valida siempre en el servidor. El cliente se puede manipular fácilmente.
  • Evita duplicar validaciones entre HTML y Python.
  • Separa responsabilidades: el formulario valida, el controlador procesa y el modelo guarda.
  • Usa mensajes de error personalizados para mejorar la experiencia de usuario.

En mi experiencia, estos pequeños ajustes marcan una gran diferencia en proyectos reales, especialmente cuando los formularios crecen en complejidad.

Ejemplo completo: formulario de tareas en Flask 3

Formulario

from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import InputRequired
class Task(FlaskForm):
   name = StringField('Name', validators=[InputRequired()])
Controlador
@app.route('/create', methods=['GET', 'POST'])
def create():
   form = Task()
   if form.validate_on_submit():
       print("Tarea creada correctamente")
   return render_template('task_form.html', form=form)
Template (task_form.html)
<form method="POST">
 {{ form.hidden_tag() }}
 {{ form.name.label }} {{ form.name() }}
 {% for error in form.name.errors %}
     <div class="error">{{ error }}</div>
 {% endfor %}
 <button type="submit">Enviar</button>
</form>

Este ejemplo refleja la base de cualquier formulario funcional en Flask 3, con validación segura y sintaxis limpia.

Conclusión

Usar WTForms en Flask 3 no solo simplifica la validación de formularios, sino que mejora la seguridad y la mantenibilidad del código.
En mi caso, este enfoque eliminó duplicaciones innecesarias y me permitió concentrarme en la lógica del negocio, no en el markup.

Si estás desarrollando con Flask 3, integrar WTForms es prácticamente obligatorio: te ofrece control, claridad y una base sólida para cualquier proyecto profesional.

❓Preguntas frecuentes

¿WTForms y Flask-WTF son lo mismo?
WTForms es la librería base; Flask-WTF es su integración con Flask.

¿Qué cambia en Flask 3?
Mejor compatibilidad con Python 3.12, estructura modular y soporte de extensiones actualizadas.

¿Cómo manejar validaciones personalizadas?
Usa métodos validate_<campo> dentro de tu clase FlaskForm.

Acepto recibir anuncios de interes sobre este Blog.

Los formularios son una parte crucial en las aplicaciones web, el paquete de WTForms proporciona una varios campos a los cuales les podemos aplicar validaciones en el lado del servidor y el cliente.

| 👤 Andrés Cruz

🇺🇸 In english