Table of Contents
Understanding Form Handling in Flask
In web development, “handling user input” usually means:
- Showing a form in the browser (HTML).
- Letting the user type something and click a button.
- Receiving that data on the server (your Flask app).
- Doing something with it (validating, saving, responding).
This chapter focuses on how to do that with Flask.
The basic request cycle for forms
When you work with forms, two HTTP methods matter most:
GET– usually used to show the form.POST– usually used when the form is submitted.
Typical pattern:
- Browser sends
GETrequest → server returns a page with a form. - User fills the form and clicks “Submit”.
- Browser sends
POSTrequest with the form fields. - Server (Flask) reads the submitted data and responds.
In Flask, you handle this with routes that accept specific methods.
Creating a simple HTML form in a Flask template
Assume you have a Flask app and templates set up (as covered earlier in the chapter on templates).
A basic HTML form in a template (for example templates/contact.html):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Contact form</title>
</head>
<body>
<h1>Contact us</h1>
<form action="/contact" method="post">
<label for="name">Your name:</label>
<input type="text" id="name" name="name">
<br>
<label for="message">Message:</label>
<textarea id="message" name="message"></textarea>
<br>
<button type="submit">Send</button>
</form>
</body>
</html>Important parts:
action="/contact"– where the form is sent (the URL path).method="post"– usePOSTto submit the data.- Each input has a
nameattribute (name="name",name="message").
This is the key Flask uses to access the values.
Defining a route that handles form submission
On the Flask side, you create a route that accepts both GET and POST:
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route("/contact", methods=["GET", "POST"])
def contact():
if request.method == "POST":
# The user submitted the form
user_name = request.form.get("name")
user_message = request.form.get("message")
# Do something with the data (for now, just show a response)
return f"Thanks, {user_name}! You wrote: {user_message}"
# If it's a GET request, show the empty form
return render_template("contact.html")Key ideas:
methods=["GET", "POST"]tells Flask this route accepts both.request.methodlets you check whether it wasGETorPOST.request.formis a dictionary-like object containing the submitted fields.
Accessing form data with `request.form`
request.form works like a dictionary:
- Keys are the
nameattributes of your form inputs. - Values are the text the user typed or selected.
Common ways to access data:
name = request.form["name"] # raises an error if 'name' is missing
email = request.form.get("email") # returns None if 'email' is missing
Using .get() is safer for beginners because it won’t crash if the field is not present. You can even provide a default:
name = request.form.get("name", "Anonymous")Handling multiple types of form fields
Different HTML input types are all accessed through request.form (or related objects). The field type is set in the HTML, but the way you read it in Flask is similar.
Text fields
HTML:
<input type="text" name="username">Flask:
username = request.form.get("username")Password fields
HTML:
<input type="password" name="password">Flask:
password = request.form.get("password")You treat it as a normal string, but in real apps you must handle it securely (hashed, never printed, etc.).
Textarea (multi-line text)
HTML:
<textarea name="bio"></textarea>Flask:
bio = request.form.get("bio")Radio buttons
HTML:
<p>Choose a color:</p>
<label>
<input type="radio" name="color" value="red"> Red
</label>
<label>
<input type="radio" name="color" value="blue"> Blue
</label>
<label>
<input type="radio" name="color" value="green"> Green
</label>Flask:
favorite_color = request.form.get("color")Only one value is sent (the selected radio button).
Checkboxes
HTML:
<label>
<input type="checkbox" name="subscribe" value="yes">
Subscribe to newsletter
</label>Flask:
subscribed = request.form.get("subscribe") # 'yes' if checked, None if notIf you have multiple checkboxes with the same name:
HTML:
<label>
<input type="checkbox" name="hobbies" value="music"> Music
</label>
<label>
<input type="checkbox" name="hobbies" value="sports"> Sports
</label>
<label>
<input type="checkbox" name="hobbies" value="reading"> Reading
</label>Flask:
selected_hobbies = request.form.getlist("hobbies")
# e.g. ['music', 'reading']
Use .getlist() when you expect multiple values for the same field name.
Select (dropdown)
HTML:
<select name="country">
<option value="us">United States</option>
<option value="uk">United Kingdom</option>
<option value="de">Germany</option>
</select>Flask:
country = request.form.get("country")
It will be one of "us", "uk", "de".
Using POST–Redirect–GET to avoid resubmission
When a user submits a form and you return a page directly, refreshing the page can cause the browser to re-submit the form.
A common pattern is:
- Handle
POSTdata. - Do your processing.
- Redirect to another page (or to the same route with
GET).
This is known as “POST–Redirect–GET”.
Example:
from flask import Flask, request, render_template, redirect, url_for
app = Flask(__name__)
messages = []
@app.route("/guestbook", methods=["GET", "POST"])
def guestbook():
if request.method == "POST":
name = request.form.get("name", "Anonymous")
text = request.form.get("text", "")
messages.append({"name": name, "text": text})
# Redirect after handling POST
return redirect(url_for("guestbook"))
# GET request: show the form and messages
return render_template("guestbook.html", messages=messages)
Template guestbook.html (simplified):
<!DOCTYPE html>
<html>
<body>
<h1>Guestbook</h1>
<form action="{{ url_for('guestbook') }}" method="post">
<input type="text" name="name" placeholder="Your name">
<br>
<textarea name="text" placeholder="Your message"></textarea>
<br>
<button type="submit">Sign</button>
</form>
<h2>Messages</h2>
<ul>
{% for msg in messages %}
<li><strong>{{ msg.name }}:</strong> {{ msg.text }}</li>
{% endfor %}
</ul>
</body>
</html>
Here, after submitting, the user is redirected back to /guestbook with GET, so refreshing only reloads the list, not re-submits the form.
Basic validation of user input
Beginners often forget that users might:
- Leave fields empty.
- Type numbers where text is expected (or vice versa).
- Enter nonsense or harmful text.
Simple validation means checking the data before using it.
Example: a simple “add numbers” form.
Template add.html:
<!DOCTYPE html>
<html>
<body>
<h1>Add two numbers</h1>
{% if error %}
<p style="color: red;">{{ error }}</p>
{% endif %}
<form action="{{ url_for('add') }}" method="post">
<input type="text" name="a" placeholder="First number">
<input type="text" name="b" placeholder="Second number">
<button type="submit">Add</button>
</form>
{% if result is not none %}
<p>Result: {{ result }}</p>
{% endif %}
</body>
</html>Flask route:
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route("/add", methods=["GET", "POST"])
def add():
error = None
result = None
if request.method == "POST":
a_str = request.form.get("a", "").strip()
b_str = request.form.get("b", "").strip()
if not a_str or not b_str:
error = "Please fill in both numbers."
else:
try:
a = float(a_str)
b = float(b_str)
result = a + b
except ValueError:
error = "Both values must be numbers."
return render_template("add.html", error=error, result=result)Key ideas:
- Use
.strip()to remove extra spaces. - Check for emptiness.
- Use
try/exceptaround number conversion. - Pass error messages back into the template.
Showing submitted data back in the template
Often you want to show the values the user submitted, for example to confirm a submission.
In the route:
@app.route("/signup", methods=["GET", "POST"])
def signup():
if request.method == "POST":
username = request.form.get("username")
email = request.form.get("email")
return render_template("signup_done.html",
username=username, email=email)
return render_template("signup_form.html")
In signup_done.html:
<!DOCTYPE html>
<html>
<body>
<h1>Signup complete</h1>
<p>Welcome, {{ username }}!</p>
<p>We will send a confirmation to {{ email }}.</p>
</body>
</html>Data flows:
- User submits form.
- Flask reads
request.form. - Flask passes values into
render_template. - Template displays them with
{{ ... }}.
Keeping track of form errors and values
A simple pattern for more complex forms is to store everything in dictionaries.
Example:
@app.route("/profile", methods=["GET", "POST"])
def profile():
errors = {}
values = {"name": "", "age": ""}
if request.method == "POST":
values["name"] = request.form.get("name", "").strip()
values["age"] = request.form.get("age", "").strip()
if not values["name"]:
errors["name"] = "Name is required."
try:
age = int(values["age"])
if age <= 0:
errors["age"] = "Age must be a positive number."
except ValueError:
errors["age"] = "Age must be an integer."
if not errors:
return f"Profile saved for {values['name']}, age {age}"
return render_template("profile.html", errors=errors, values=values)
Template profile.html (simplified):
<!DOCTYPE html>
<html>
<body>
<h1>Edit profile</h1>
<form method="post">
<label>Name:
<input type="text" name="name" value="{{ values.name }}">
</label>
{% if errors.name %}
<span style="color:red">{{ errors.name }}</span>
{% endif %}
<br>
<label>Age:
<input type="text" name="age" value="{{ values.age }}">
</label>
{% if errors.age %}
<span style="color:red">{{ errors.age }}</span>
{% endif %}
<br>
<button type="submit">Save</button>
</form>
</body>
</html>Here:
valueskeeps the user’s input so you can re-show it after an error.errorsstores messages for each field.
Handling query parameters (simple GET input)
Not all user input comes from forms with method="post". Sometimes data is passed in the URL, for example:
/search?q=python/page?number=3
These are called query parameters and are accessed with request.args.
Example:
from flask import Flask, request
app = Flask(__name__)
@app.route("/search")
def search():
query = request.args.get("q", "")
return f"You searched for: {query}"
If the user visits /search?q=flask, then query is "flask".
Use this when:
- You don’t need a form, or
- You want bookmarkable URLs (search, filters, etc.).
Very brief note on security
Even in small practice apps, remember:
- Never trust user input.
- Validate and clean data before using it.
- Be careful with printing or storing sensitive data (like passwords).
- In real applications, you would also add protection against CSRF (Cross-Site Request Forgery) and other attacks.
For this beginner course, you focus on the mechanics of receiving and using data, but keep in mind that security matters as you move to more serious projects.
Summary
In this chapter you saw how to:
- Build basic HTML forms in templates.
- Use
methods=["GET", "POST"]in Flask routes. - Read form data with
request.formandrequest.form.getlist. - Distinguish between showing the form (
GET) and processing it (POST). - Use POST–Redirect–GET to avoid duplicate submissions.
- Perform simple validation and show errors.
- Refill forms with previous values after errors.
- Read query parameters with
request.args.
These tools let you build interactive web pages where users can enter data and your Python code can respond to it.