Forms, sessions and errors

Reading request data, validating it, flashing messages, keeping a session, and returning sensible error responses.

Reading the request

from flask import request, abort, redirect, url_for, flash, session

@app.post("/subscribe")
def subscribe():
    email = request.form.get("email", "").strip()
    if "@" not in email:
        flash("Enter a valid email address", "error")
        return redirect(url_for("index"))

    page = request.args.get("page", 1, type=int)      # query string, cast
    payload = request.get_json(silent=True) or {}     # JSON body, no 400 on bad JSON
    ua = request.headers.get("User-Agent", "")
    return {"email": email, "page": page}
SourceAccessor
Form bodyrequest.form
Query stringrequest.args
JSON bodyrequest.get_json()
Uploaded filesrequest.files
Cookies / headersrequest.cookies / request.headers
⚠️
Every value from the client is untrusted text. Validate type, length and range on the server — HTML form attributes are a convenience, not a control.

Sessions and messages

@app.post("/login")
def login():
    if not check_credentials(request.form["user"], request.form["pw"]):
        abort(401)
    session.clear()                 # prevent session fixation
    session["uid"] = 7
    return redirect(url_for("dashboard"))

@app.get("/dashboard")
def dashboard():
    if "uid" not in session:
        return redirect(url_for("login"))
    return render_template("dashboard.html")

Flask's default session is a signed cookie: the client can read it but not modify it. Anything secret or large belongs in a server-side store keyed by a session id.

Errors and API responses

@app.errorhandler(404)
def not_found(err):
    if request.path.startswith("/api/"):
        return {"error": "not found"}, 404
    return render_template("404.html"), 404

@app.errorhandler(500)
def server_error(err):
    app.logger.exception("unhandled error")
    return {"error": "internal error"}, 500

from flask import jsonify
return jsonify(ok=True), 201
💡
Return the correct status code with the body: 200 with {"error": ...} forces every client to parse JSON to learn that something failed.

FAQ

How do I keep the user's input in the form after an error?
Re-render the template with the submitted values, or use the flash pattern with a redirect and have the form read from the session.
Where do I put CSRF protection?
Use Flask-WTF (or an equivalent) and include the token in every state-changing form. Flask itself does not add CSRF tokens automatically.

Templates with Jinja2 Forms, sessions and safety

Last refreshed 2026-09-18.