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 defhandlers - Signals for decoupled event notification (via
blinker) - Class-based views with
ViewandMethodView - Comprehensive testing utilities with
FlaskClientandFlaskCliRunner
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
--debugto 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 defview functions, install theasyncextra:pip install "flask[async]". This pulls inasgiref. Without it, async views raiseRuntimeError: 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. |
Session Cookie Configuration
| 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, andfrom_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
.flaskenvfile in your project root for development defaults. Flask loads it automatically whenpython-dotenvis installed..envtakes 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
HEADandOPTIONSmethods to routes. Useflask routes --all-methodsto 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 ofrender_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_KEYmust be set for sessions to work. Without it, Flask raisesRuntimeError: 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("", ...)orBlueprint("my.bp", ...)raisesValueError.
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:
- Ensure your app file is named
app.pyorwsgi.py, or specify it explicitly:flask --app myapp run - If using a factory, the function must be named
create_appormake_app, or specify it:flask --app "myapp:create_app()" run - 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:
- Check the file exists at
templates/<name>.htmlrelative to your app module. - For blueprints, templates are at
<blueprint_package>/templates/. - Enable
EXPLAIN_TEMPLATE_LOADINGto 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 runorapp.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.