Day 3 of 5
⏱ ~60 minutes
Flask in 5 Days — Day 3

Forms and Validation

Handle HTML forms with WTForms, validate input server-side, handle file uploads, and display flash messages.

WTForms Integration

Terminal
pip install flask-wtf
app.py
app.config['SECRET_KEY'] = 'your-secret-key'  # required for CSRF
forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, EmailField
from wtforms.validators import DataRequired, Email, Length

class ContactForm(FlaskForm):
    name = StringField('Name', validators=[DataRequired(), Length(min=2, max=50)])
    email = EmailField('Email', validators=[DataRequired(), Email()])
    message = TextAreaField('Message', validators=[DataRequired(), Length(min=10)])
views.py
from .forms import ContactForm
from flask import flash

@app.route('/contact', methods=['GET', 'POST'])
def contact():
    form = ContactForm()
    if form.validate_on_submit():
        # form is valid and it was a POST
        flash('Message sent!', 'success')
        return redirect(url_for('index'))
    return render_template('contact.html', form=form)
templates/contact.html
{{ form.hidden_tag() }} {# CSRF token #}
{{ form.name.label }} {{ form.name(class='input') }} {% for error in form.name.errors %} {{ error }} {% endfor %}
{{ form.message.label }} {{ form.message() }}
📝 Day 3 Exercise
Build a Contact Form
  1. C
  2. r
  3. e
  4. a
  5. t
  6. e
  7. a
  8. C
  9. o
  10. n
  11. t
  12. a
  13. c
  14. t
  15. F
  16. o
  17. r
  18. m
  19. w
  20. i
  21. t
  22. h
  23. W
  24. T
  25. F
  26. o
  27. r
  28. m
  29. s
  30. v
  31. a
  32. l
  33. i
  34. d
  35. a
  36. t
  37. o
  38. r
  39. s
  40. .
  41. D
  42. i
  43. s
  44. p
  45. l
  46. a
  47. y
  48. i
  49. n
  50. l
  51. i
  52. n
  53. e
  54. e
  55. r
  56. r
  57. o
  58. r
  59. s
  60. p
  61. e
  62. r
  63. f
  64. i
  65. e
  66. l
  67. d
  68. .
  69. S
  70. h
  71. o
  72. w
  73. a
  74. f
  75. l
  76. a
  77. s
  78. h
  79. m
  80. e
  81. s
  82. s
  83. a
  84. g
  85. e
  86. o
  87. n
  88. s
  89. u
  90. c
  91. c
  92. e
  93. s
  94. s
  95. .
  96. R
  97. e
  98. d
  99. i
  100. r
  101. e
  102. c
  103. t
  104. a
  105. f
  106. t
  107. e
  108. r
  109. P
  110. O
  111. S
  112. T
  113. t
  114. o
  115. p
  116. r
  117. e
  118. v
  119. e
  120. n
  121. t
  122. d
  123. o
  124. u
  125. b
  126. l
  127. e
  128. -
  129. s
  130. u
  131. b
  132. m
  133. i
  134. s
  135. s
  136. i
  137. o
  138. n
  139. .

Day 3 Summary

  • Flask-WTF wraps WTForms with CSRF protection. Always add form.hidden_tag() to your forms.
  • form.validate_on_submit() returns True only on valid POST requests — the one method you need.
  • Flash messages: flash('message', 'category') in the view, get_flashed_messages() in the template.
  • Post-Redirect-Get (PRG) pattern: after a successful POST, redirect. Prevents form resubmission on refresh.
Finished this lesson?