Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

Flask — User Guide

Generated May 2026 from pallets/flask at 2ac8988

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

1. Quick Start

Flask is a lightweight Python web framework that provides the essentials for building web applications — routing, request handling, templating, and a development server — without imposing database, form validation, or authentication choices on you.

Key features:

  • Decorator-based URL routing with variable rules and HTTP method support
  • Jinja2 templating with autoescaping and template inheritance
  • Built-in development server with automatic reloading and interactive debugger
  • Secure cookie-based sessions signed with itsdangerous
  • Blueprints for modular application structure
  • CLI powered by Click with extensible commands
  • Async view support via async def handlers
  • Signals for decoupled event notification (via blinker)
  • Class-based views with View and MethodView
  • Comprehensive testing utilities with FlaskClient and FlaskCliRunner

Prerequisites: Python 3.9+

Install and run:

pip install flask
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello, World!"
flask --app hello run
 * Serving Flask app 'hello'
 * Debug mode: off
 * Running on http://127.0.0.1:5000

Tip

Add --debug to enable the interactive debugger and automatic code reloading: flask --app hello run --debug

What to try next: See Installation for all install methods, Configuration for essential settings, or jump to Core Usage for routing and view patterns.

2. Installation

System Requirements

Requirement Minimum
Python 3.9+
OS Linux, macOS, Windows

Install Methods

pip

pip install flask

pip (with async support)

pip install "flask[async]"

uv

uv add flask

conda

conda install -c conda-forge flask

From source

git clone https://github.com/pallets/flask.git
cd flask
pip install -e .

Important

To use async def view functions, install the async extra: pip install "flask[async]". This pulls in asgiref. Without it, async views raise RuntimeError: Install Flask with the 'async' extra in order to use async views.

Core Dependencies

Flask automatically installs these dependencies:

Package Purpose
Werkzeug WSGI toolkit — request/response handling, routing, dev server
Jinja2 Template engine with autoescaping
MarkupSafe Escapes untrusted input for safe HTML rendering
itsdangerous Cryptographically signs session cookies
click CLI framework powering the flask command
blinker Signal support for decoupled event notification

Post-Install Verification

flask --version
Python 3.13.x
Flask 3.1.x
Werkzeug 3.1.x

Install Troubleshooting

Q: flask command not found after install?

Ensure the Python scripts directory is in your PATH. If using a virtual environment, activate it first:

python -m venv .venv
source .venv/bin/activate  # Linux/macOS
.venv\Scripts\activate     # Windows
pip install flask

Q: ModuleNotFoundError: No module named 'flask'?

You may have multiple Python installations. Verify with:

python -c "import flask; print(flask.__version__)"

Q: Import errors from markupsafe or jinja2?

Upgrade all dependencies together:

pip install --upgrade flask werkzeug jinja2 markupsafe

3. Configuration

Flask uses uppercase Python attributes as configuration keys. Set them on the app.config object, which behaves like a dictionary.

Essential Configuration Keys

Key Type Default Description
SECRET_KEY str | None None Signs session cookies. Must be set for sessions to work.
DEBUG bool | None None Enables debug mode. Typically set via FLASK_DEBUG env var.
TESTING bool False Enable test mode. Exceptions propagate instead of being handled.
SERVER_NAME str | None None Host and port for URL generation outside requests (e.g. localhost:5000).
APPLICATION_ROOT str "/" Path prefix when the app is mounted at a sub-path.
MAX_CONTENT_LENGTH int | None None Maximum request body size in bytes. Returns 413 if exceeded.
PREFERRED_URL_SCHEME str "http" URL scheme for url_for when outside a request.
Key Type Default Description
SESSION_COOKIE_NAME str "session" Name of the session cookie.
SESSION_COOKIE_DOMAIN str | None None Domain to set on the cookie.
SESSION_COOKIE_PATH str | None None Path scope of the cookie.
SESSION_COOKIE_HTTPONLY bool True Prevent JavaScript access to the cookie.
SESSION_COOKIE_SECURE bool False Only send cookie over HTTPS.
SESSION_COOKIE_SAMESITE str | None None SameSite attribute ("Lax", "Strict", "None").
SESSION_COOKIE_PARTITIONED bool False Enable partitioned cookies (CHIPS).
PERMANENT_SESSION_LIFETIME timedelta 31 days How long a permanent session lasts.
SESSION_REFRESH_EACH_REQUEST bool True Re-send the cookie on every response to refresh the expiration.

Setting Configuration

# Direct assignment
app.config["SECRET_KEY"] = "change-me-in-production"
app.config["MAX_CONTENT_LENGTH"] = 16 * 1024 * 1024  # 16 MB

# From a Python file
app.config.from_pyfile("settings.py")

# From environment variables with FLASK_ prefix
app.config.from_prefixed_env()
# FLASK_SECRET_KEY=supersecret -> app.config["SECRET_KEY"]

# From JSON or TOML files
import json
app.config.from_file("config.json", load=json.load)

import tomllib
app.config.from_file("config.toml", load=tomllib.load, text=False)

# From a Python object or import path
app.config.from_object("yourapplication.default_config")

Warning

Only uppercase keys are loaded by from_object, from_pyfile, and from_mapping. Lowercase keys are silently ignored — this is by design, allowing you to use lowercase variables as local helpers in config files.

Environment Variables

Variable Purpose
FLASK_APP Module or import path to locate the application (e.g. myapp or myapp:create_app).
FLASK_DEBUG Set to 1 to enable debug mode. Overrides the DEBUG config key.
FLASK_SKIP_DOTENV Set to 1 to skip loading .env and .flaskenv files.

Tip

Create a .flaskenv file in your project root for development defaults. Flask loads it automatically when python-dotenv is installed. .env takes precedence over .flaskenv, and the actual environment takes precedence over both.

See the Admin Guide for the complete configuration reference including all keys, TRUSTED_HOSTS, template auto-reload, and advanced session settings.

4. Core Usage

Defining Routes

Register URL rules with the @app.route() decorator. Each route maps a URL pattern to a Python function:

@app.route("/")
def index():
    return "Hello, World!"

@app.route("/users/<username>")
def show_user(username):
    return f"User: {username}"

@app.route("/posts/<int:post_id>")
def show_post(post_id):
    return f"Post #{post_id}"

URL variable converters:

Converter Description Example
string Default — matches text without slashes <username>
int Matches positive integers <int:id>
float Matches positive floating-point values <float:price>
path Matches text including slashes <path:subpath>
uuid Matches UUID strings <uuid:item_id>

HTTP Methods

@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        return do_login()
    return show_login_form()

Shorthand decorators for common methods:

@app.get("/items")
def list_items():
    return get_all_items()

@app.post("/items")
def create_item():
    return save_item(request.json)

@app.put("/items/<int:id>")
def update_item(id):
    return modify_item(id, request.json)

@app.delete("/items/<int:id>")
def delete_item(id):
    return remove_item(id)

@app.patch("/items/<int:id>")
def patch_item(id):
    return partial_update(id, request.json)

Note

Flask automatically adds HEAD and OPTIONS methods to routes. Use flask routes --all-methods to see these in the route listing.

Request Data

Access incoming request data through the request proxy:

from flask import request

@app.route("/search")
def search():
    query = request.args.get("q", "")          # Query string: /search?q=flask
    page = request.args.get("page", 1, int)    # With type conversion
    return f"Searching for: {query}, page {page}"

@app.route("/submit", methods=["POST"])
def submit():
    name = request.form["name"]                # Form data
    file = request.files["avatar"]             # Uploaded file
    data = request.json                        # JSON body (parsed)
    raw = request.data                         # Raw body bytes
    return "OK"

Response Types

View functions can return several types that Flask converts to a proper HTTP response:

Return Type Behavior
str Becomes the response body (UTF-8 encoded)
bytes Becomes the response body directly
dict or list Automatically serialized to JSON
tuple (body, status), (body, headers), or (body, status, headers)
Response Returned as-is
Generator/Iterator Streamed as a response
@app.route("/json")
def json_example():
    return {"message": "hello", "status": "ok"}  # Auto-JSON

@app.route("/custom")
def custom_response():
    return "Created", 201, {"X-Custom": "value"}

@app.route("/explicit")
def explicit_response():
    from flask import make_response
    resp = make_response("Hello")
    resp.headers["X-Custom"] = "value"
    return resp

Using jsonify

For more control over JSON responses, use jsonify:

from flask import jsonify

@app.route("/api/users")
def api_users():
    users = [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]
    return jsonify(users)

Templates (Jinja2)

Render HTML templates from the templates/ folder:

from flask import render_template

@app.route("/hello/<name>")
def hello(name):
    return render_template("hello.html", name=name)

Template file templates/hello.html:

<!doctype html>
<html>
  <body>
    <h1>Hello, {{ name }}!</h1>
  </body>
</html>

Templates have automatic access to request, session, g, config, url_for(), and get_flashed_messages().

Tip

Use stream_template() instead of render_template() when generating large pages to send output incrementally to the client without buffering the entire response in memory.

Redirects and Errors

from flask import redirect, url_for, abort

@app.route("/old-page")
def old_page():
    return redirect(url_for("new_page"))

@app.route("/admin")
def admin():
    if not is_admin():
        abort(403)  # Forbidden
    return "Admin Panel"

URL Building with url_for

Generate URLs by endpoint name instead of hardcoding paths:

from flask import url_for

url_for("index")                          # "/"
url_for("show_user", username="alice")    # "/users/alice"
url_for("static", filename="style.css")  # "/static/style.css"
url_for("index", _external=True)         # "http://localhost:5000/"
url_for("index", _anchor="section")      # "/#section"

Flash Messages

Display one-time messages to users across redirects:

from flask import flash, get_flashed_messages

@app.route("/save", methods=["POST"])
def save():
    # ... save data ...
    flash("Changes saved successfully!")
    return redirect(url_for("index"))

In the template:

{% for message in get_flashed_messages() %}
  <div class="alert">{{ message }}</div>
{% endfor %}

Static Files

Place static files (CSS, JavaScript, images) in the static/ folder. Flask serves them at /static/ by default:

<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">

Sessions

Flask provides signed cookie-based sessions. Data stored in session is cryptographically signed but not encrypted — users can read but not modify it:

from flask import session

app.config["SECRET_KEY"] = "your-secret-key"

@app.route("/login", methods=["POST"])
def login():
    session["user_id"] = user.id
    session.permanent = True  # Lasts PERMANENT_SESSION_LIFETIME (default: 31 days)
    return redirect(url_for("index"))

@app.route("/logout")
def logout():
    session.clear()
    return redirect(url_for("index"))

Warning

The SECRET_KEY must be set for sessions to work. Without it, Flask raises RuntimeError: The session is unavailable because no secret key was set. Use a cryptographically random value in production.

5. Common Tasks

Creating an Application Factory

Use a factory function to create and configure the app. This is the recommended pattern for larger applications:

# myapp/__init__.py
from flask import Flask

def create_app(test_config=None):
    app = Flask(__name__, instance_relative_config=True)
    app.config.from_mapping(
        SECRET_KEY="dev",
    )

    if test_config is None:
        app.config.from_pyfile("config.py", silent=True)
    else:
        app.config.from_mapping(test_config)

    @app.route("/hello")
    def hello():
        return "Hello, World!"

    return app

Run it with:

flask --app myapp run

Organizing with Blueprints

Split a large application into reusable modules:

# myapp/auth.py
from flask import Blueprint, render_template, redirect, url_for

auth_bp = Blueprint("auth", __name__, url_prefix="/auth")

@auth_bp.route("/login", methods=["GET", "POST"])
def login():
    return render_template("auth/login.html")

@auth_bp.route("/logout")
def logout():
    return redirect(url_for("index"))

Register in your app factory:

def create_app():
    app = Flask(__name__)
    from .auth import auth_bp
    app.register_blueprint(auth_bp)
    return app

Note

Blueprint names cannot be empty or contain dots. Attempting Blueprint("", ...) or Blueprint("my.bp", ...) raises ValueError.

Custom Error Pages

@app.errorhandler(404)
def page_not_found(e):
    return render_template("404.html"), 404

@app.errorhandler(500)
def server_error(e):
    return render_template("500.html"), 500

# Handle custom exception types
@app.errorhandler(ValueError)
def handle_value_error(e):
    return {"error": str(e)}, 400

File Uploads

from flask import request
import os

UPLOAD_FOLDER = "/path/to/uploads"
app.config["MAX_CONTENT_LENGTH"] = 16 * 1024 * 1024  # 16 MB

@app.route("/upload", methods=["POST"])
def upload_file():
    file = request.files["file"]
    if file.filename:
        file.save(os.path.join(UPLOAD_FOLDER, file.filename))
        return "File uploaded", 201
    return "No file selected", 400

Sending Files

from flask import send_file, send_from_directory

@app.route("/download/<filename>")
def download(filename):
    return send_from_directory("uploads", filename, as_attachment=True)

Using Class-Based Views

For more structured request handling, use MethodView:

from flask.views import MethodView

class UserAPI(MethodView):
    def get(self, user_id):
        if user_id is None:
            return get_all_users()
        return get_user(user_id)

    def post(self):
        return create_user(request.json)

    def put(self, user_id):
        return update_user(user_id, request.json)

    def delete(self, user_id):
        return delete_user(user_id)

user_view = UserAPI.as_view("users")
app.add_url_rule("/users/", defaults={"user_id": None}, view_func=user_view, methods=["GET"])
app.add_url_rule("/users/", view_func=user_view, methods=["POST"])
app.add_url_rule("/users/<int:user_id>", view_func=user_view, methods=["GET", "PUT", "DELETE"])

Request Hooks

Execute code before and after requests:

@app.before_request
def require_login():
    if request.endpoint != "login" and "user_id" not in session:
        return redirect(url_for("login"))

@app.after_request
def add_security_headers(response):
    response.headers["X-Content-Type-Options"] = "nosniff"
    return response

@app.teardown_request
def close_db(exception):
    db = g.pop("db", None)
    if db is not None:
        db.close()

Async Views

With the async extra installed, use async def for views:

@app.route("/async-data")
async def async_data():
    data = await fetch_data_async()
    return {"data": data}

Streaming Responses

from flask import stream_with_context, Response

@app.route("/stream")
def stream():
    def generate():
        for i in range(100):
            yield f"data: {i}\n\n"
    return Response(stream_with_context(generate()), content_type="text/event-stream")

Custom CLI Commands

Register custom commands on the Flask CLI:

import click

@app.cli.command("init-db")
def init_db_command():
    """Initialize the database."""
    init_db()
    click.echo("Database initialized.")
flask --app myapp init-db
Database initialized.

Writing Tests

import pytest
from myapp import create_app

@pytest.fixture
def app():
    app = create_app({"TESTING": True, "SECRET_KEY": "test"})
    yield app

@pytest.fixture
def client(app):
    return app.test_client()

def test_hello(client):
    response = client.get("/hello")
    assert response.status_code == 200
    assert b"Hello" in response.data

def test_json_post(client):
    response = client.post("/api/items", json={"name": "test"})
    assert response.status_code == 201

Tip

Use app.test_client() as a context manager to access request context locals after the response: with app.test_client() as c: rv = c.get("/"); assert request.path == "/"

Testing CLI Commands

def test_init_db(app):
    runner = app.test_cli_runner()
    result = runner.invoke(args=["init-db"])
    assert "Database initialized" in result.output

6. Troubleshooting

Error: Could not locate a Flask application

Cause: The flask command cannot find your app. It looks for app.py or wsgi.py in the current directory, or uses FLASK_APP / --app.

Solution:

  1. Ensure your app file is named app.py or wsgi.py, or specify it explicitly:
    flask --app myapp run
    
  2. If using a factory, the function must be named create_app or make_app, or specify it:
    flask --app "myapp:create_app()" run
    
  3. Ensure your working directory contains the application module.

Prevention: Always use flask --app <module> explicitly in production scripts.

Error: The session is unavailable because no secret key was set

Cause: You accessed session without configuring SECRET_KEY.

Solution:

app.config["SECRET_KEY"] = "a-random-secret-key"

Prevention: Set SECRET_KEY early in your app factory or config file.

Error: RuntimeError: Install Flask with the 'async' extra

Cause: You used an async def view function without installing asgiref.

Solution:

pip install "flask[async]"

Error: View function mapping is overwriting an existing endpoint function

Cause: Two routes share the same endpoint name (function name by default).

Solution: Give each route a unique function name, or set the endpoint argument:

@app.route("/page1")
def page_one():
    return "Page 1"

@app.route("/page2")
def page_two():  # Not 'page' again
    return "Page 2"

Error: TemplateNotFound

Cause: Flask cannot find the template file. By default, templates are loaded from the templates/ folder relative to your application's root path.

Solution:

  1. Check the file exists at templates/<name>.html relative to your app module.
  2. For blueprints, templates are at <blueprint_package>/templates/.
  3. Enable EXPLAIN_TEMPLATE_LOADING to see where Flask looks:
    app.config["EXPLAIN_TEMPLATE_LOADING"] = True
    

Error: 413 Request Entity Too Large

Cause: The request body exceeds MAX_CONTENT_LENGTH.

Solution: Increase the limit or handle the error:

app.config["MAX_CONTENT_LENGTH"] = 64 * 1024 * 1024  # 64 MB

Error: BuildError when using url_for

Cause: The endpoint does not exist, or required URL variable values were not provided.

Solution: Verify the endpoint name matches your view function or endpoint= argument. For blueprint routes, prefix with the blueprint name: url_for("auth.login").

Werkzeug compatibility issues (#5961)

Cause: Flask tests may break when Werkzeug updates change internal behavior (e.g., IDNA handling).

Solution: Pin compatible Werkzeug versions in your requirements or let Flask's dependency constraints handle it:

flask>=3.1
werkzeug>=3.1,<4.0

Warning

Always test your application after upgrading Werkzeug. Flask and Werkzeug are developed together but minor version bumps can occasionally change behavior.

Caution

Do not use flask run or app.run() in production. The development server is not designed for security or performance. See the FAQ for production deployment options.

7. FAQ

Q: How do I run Flask in production?

Do not use flask run or app.run() in production. Use a production WSGI server such as Gunicorn, Waitress, or uWSGI. See the Admin Guide for deployment details.

pip install gunicorn
gunicorn -w 4 "myapp:create_app()"

Q: How do I enable debug mode?

Set the FLASK_DEBUG environment variable or use the --debug flag:

export FLASK_DEBUG=1
flask --app myapp run
# or
flask --app myapp run --debug

Q: How do I serve the app on all network interfaces?

flask run --host 0.0.0.0

Q: How do I change the default port?

flask run --port 8080

Q: How do I use HTTPS during development?

Generate an ad-hoc certificate (requires the cryptography package):

pip install cryptography
flask run --cert adhoc

Or provide your own certificate and key:

flask run --cert cert.pem --key key.pem

Q: How do I access the app inside the interactive shell?

flask --app myapp shell

This opens a Python REPL with app, g, and any registered shell context processors pre-loaded.

Q: Can I use .env files for configuration?

Yes, install python-dotenv:

pip install python-dotenv

Flask automatically loads .flaskenv and .env files from the project directory. .env overrides .flaskenv. You can also specify a custom env file:

flask --env-file .env.local run

Q: How do I list all registered routes?

flask routes
Endpoint  Methods  Rule
--------  -------  ---------
index     GET      /
static    GET      /static/<path:filename>

Use --sort rule to sort by URL pattern, or --all-methods to include HEAD and OPTIONS.

Q: How do I handle CORS?

Flask does not include CORS handling. Use the flask-cors extension:

pip install flask-cors
from flask_cors import CORS
CORS(app)

Q: What is the g object?

g is a per-request namespace for storing data during request processing. It is commonly used to hold database connections or authenticated user objects. Data stored in g is available only during the current request and cleared automatically afterward.

Q: How do I register CLI commands from extensions or plugins?

Extensions can register commands via the flask.commands entry point group in their pyproject.toml. Flask discovers and loads these automatically.