Handling Forms in Flask

Read, validate, and never trust user input

Intermediate 12 min

In this lesson

An HTML form is how visitors send data to your site — a new note, a search, a sign-up — and Flask hands you the submitted values in request.form. This lesson covers reading that input and, just as importantly, validating it before you trust it.

Explain it like I’m 5

A form is a little mail-in card from the browser to your Python app. Each field has a name, and your app reads the answers by those names.

Forms send named fields to your app

A form posts its fields to a route. Each <input> has a name, and that name is the key your Flask code uses to read the value. A form that changes data should use method="post", and the route must allow POST.

Example
<form method="post" action="/add">
  <input name="note">
  <button type="submit">Add</button>
</form>
The input's name ("note") is the key your code reads.
Example
# app.py
from flask import Flask, request, redirect

app = Flask(__name__)
notes = []

@app.route("/add", methods=["GET", "POST"])
def add():
    if request.method == "POST":
        notes.append(request.form["note"])
        return redirect("/")
    return "Send a POST to add a note"
methods=[...] lets the route accept POST; request.form reads fields.

Reading and validating input

request.form behaves like a dictionary of submitted fields, so reading a value is a familiar lookup. But submitted text can be blank, padded with spaces, or junk — so clean and check it before using it.

Example
# request.form behaves like a dict of submitted fields
form = {"note": "  Buy milk  ", "priority": "high"}

note = form["note"].strip()
if note:
    print("Saved:", note)
else:
    print("Note was empty - not saved")
Output
Saved: Buy milk
Strip and check before trusting any submitted value.

Reject what you shouldn’t save

The most common validation is simply refusing blank input.

A form submitted a task field. Read it, strip the whitespace, and print "Added: <task>" only if it isn't blank — otherwise print "Rejected blank task".

form = {"task": "   "}   # imagine the user submitted only spaces

task = ""  # TODO: read form["task"] and strip it
# TODO: print "Added: <task>" if task is non-empty, else "Rejected blank task"

Common mistake: Forgetting methods=["GET", "POST"]

Why it happens:

Routes accept only GET by default, so a form POST seems to break for no reason.

How to fix it:

Add methods=["GET", "POST"] to the route that receives the form, or the submission returns a 405 Method Not Allowed.

Common mistake: Reading the wrong input name

Why it happens:

The form’s name and the key in request.form must match exactly, and typos slip in.

How to fix it:

Keep the <input name="..."> and request.form["..."] spelled identically. Using request.form.get("note", "") avoids a crash when it’s missing.

Common mistake: Trusting blank or messy input

Why it happens:

It works when you type a tidy value, so validation feels optional.

How to fix it:

Strip and validate every field. Reject empty values, and treat all submitted data as untrusted until you’ve checked it.

Where does Flask put submitted form values?

Why must you validate user input?

What’s different about a POST request compared to a GET?

Mini exercise (medium)

Write add_note(notes, form) that reads form["note"], strips it, and appends it to the notes list only when it’s non-blank. Return the list either way.

Do it here. Finish the code below and press Run to try your answer right in the browser — no setup needed.

def add_note(notes, form):
    note = ""  # TODO: read form["note"] safely and strip it
    # TODO: append to notes only if note is non-blank
    return notes

notes = ["First"]
add_note(notes, {"note": "  Second  "})
add_note(notes, {"note": "   "})
print(notes)

What to learn next

You handled form input: an HTML form posts named fields, request.form reads them like a dict, methods=["GET", "POST"] lets a route accept the submission, and you strip and validate before trusting anything. You wrote add_note() to save only non-blank notes.

Your app works — now make it look right. Static Files, Styling, and Simple Layout shows where CSS and JS live and how url_for links them. When you’re ready, continue to the next lesson.