Flask cheat sheet
A scannable Flask reference: 16 short snippets across 7 topics, each linking back to the lesson it came from.
At a glance
| Topic | What it covers | |
|---|---|---|
| Your first Flask app | Flask matches the most specific rule and raises on ambiguity. Using url_for means renaming a route does not break your | lesson |
| Templates with Jinja2 | Output is auto-escaped: {{ value }} renders HTML special characters safely, which is what protects you from XSS | lesson |
| Configuration and project structure | Organise config as classes, read secrets from the environment, keep machine-specific files in the instance folder, and | lesson |
| Schema migrations with Flask-Migrate | Initialise Alembic through Flask-Migrate, review the scripts autogenerate produces, and apply schema and data changes | lesson |
| Building and testing a JSON API | Return consistent JSON with honest status codes, then test the contract with the test client, fixtures and a database | lesson |
| File uploads, static files and sending email | Accept uploads without writing an attacker-controlled filename, cache static assets correctly, and send mail without | lesson |
| Deployment: gunicorn, nginx and Docker | Run Flask under a real WSGI server, put nginx in front for TLS and static files, and containerise the app without | lesson |
Quick snippets
Your first Flask app
The smallest app
pip install flask
python app.py # http://127.0.0.1:5000
flask --app app run --debug # the CLI way
Configuration and project layout
myapp/
app.py
config.py
templates/
static/
requirements.txt
Configuration and project layout
import os
app.config.update(
SECRET_KEY=os.environ["SECRET_KEY"], # required for sessions and flash
DEBUG=False,
JSON_SORT_KEYS=False,
)Full lesson: Your first Flask app →
Templates with Jinja2
Rendering
from flask import render_template
@app.get("/posts")
def posts():
items = [{"title": "Hello", "slug": "hello"}]
return render_template("posts.html", posts=items, user=None)
Template inheritance
{# templates/base.html #}
<!DOCTYPE html>
<html lang="en">
<head><title>{% block title %}Site{% endblock %}</title></head>
<body>
<header>{% include "_nav.html" %}</header>
<main>{% block content %}{% endblock %}</main>
</body>
</html>
Template inheritance
{# templates/posts.html #}
{% extends "base.html" %}
{% block title %}Posts · Site{% endblock %}
{% block content %}
<h1>Posts</h1>
{% endblock %}Full lesson: Templates with Jinja2 →
Configuration and project structure
Development, test and production
export FLASK_CONFIG=production
export SECRET_KEY="$(python -c 'import secrets; print(secrets.token_hex(32))')"
export DATABASE_URL="postgresql+psycopg://app:pw@db:5432/app"
flask --app app:create_app runFull lesson: Configuration and project structure →
Schema migrations with Flask-Migrate
init, migrate, upgrade
pip install flask-migrate
flask db init # creates migrations/ + a starter script
flask db migrate -m "add post.published"
flask db upgrade # apply pending revisions
flask db current # revision the database is on
flask db history --verbose # the whole chain
flask db downgrade -1 # back one revision
flask db revision -m "manual change" --autogenerate
flask db revision -m "data fix" # empty script to edit by hand
init, migrate, upgrade
migrations/
alembic.ini # generated, points at the app metadata
env.py # loads the Flask app, wires target_metadata
versions/
a1b2c3d4_add_post_published.py
b2c3d4e5_add_tags_table.py
Reviewing and editing the generated script
def upgrade():
# data migration in the same script: run SQL, then tighten the constraint
op.execute("UPDATE posts SET slug = lower(replace(title, ' ', '-')) WHERE slug IS NULL")
op.alter_column("posts", "slug", nullable=False)
# or insert reference rows, with the table's real columns
op.bulk_insert(
sa.table("tags", sa.column("id", sa.Integer), sa.column("name", sa.String)),
[{"id": 1, "name": "flask"}, {"id": 2, "name": "python"}],
)Full lesson: Schema migrations with Flask-Migrate →
Building and testing a JSON API
Edge cases worth a test
coverage run -m pytest && coverage report --skip-covered --fail-under=85
pytest -x -q --maxfail=1 # stop at the first failure while iteratingFull lesson: Building and testing a JSON API →
File uploads, static files and sending email
Static assets and caching
<link rel="stylesheet" href="{{ url_for('static', filename='css/app.css') }}">
<img src="{{ url_for('static', filename='img/logo.svg') }}" alt="Logo">
<!-- cache buster when the file has no content hash in its name -->
<img src="{{ url_for('static', filename='img/chart.png', v=asset_version) }}" alt="Chart">
{# asset_version is a build id such as a commit sha, injected by a context processor #}
Static assets and caching
# per-response caching for everything Flask serves from static/
app.config["SEND_FILE_MAX_AGE_DEFAULT"] = 0 # dev: no caching
# production: hand /static/ to nginx or a CDN and let it set the header
app.config["SEND_FILE_MAX_AGE_DEFAULT"] = 60 * 60 * 24 * 365Full lesson: File uploads, static files and sending email →
Deployment: gunicorn, nginx and Docker
WSGI servers and gunicorn
pip install gunicorn
# factory pattern: resolve create_app() at start-up
gunicorn -w 4 -k gthread --threads 4 -b 127.0.0.1:8000 --timeout 60 \
--access-logfile - --error-logfile - "app:create_app()"
# simpler layouts can point at a module-level app object
gunicorn -w 4 -b 127.0.0.1:8000 run:app
# reload workers without dropping connections
kill -HUP "$(cat /run/gunicorn.pid)"
nginx in front
# trust the proxy headers, or request.remote_addr is 127.0.0.1
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1)
# ProxyFix rewrites the scheme, so url_for(_external=True) generates https
# and request.remote_addr, request.host and request.is_secure become correct.
# Only ever wrap the app once, for the exact number of trusted proxies.
Containers and release hygiene
docker build -t registry.example.com/app:1.4.2 .
# secrets arrive at run time, not at build time
docker run --rm -p 8000:8000 \
-e FLASK_CONFIG=production \
-e SECRET_KEY="$(cat /run/secrets/flask_key)" \
-e DATABASE_URL="postgresql+psycopg://app:pw@db:5432/app" \
registry.example.com/app:1.4.2
# schema first, then the new code, from a one-off container
docker run --rm -e FLASK_CONFIG=production registry.example.com/app:1.4.2 flask db upgradeFull lesson: Deployment: gunicorn, nginx and Docker →
FAQ
Is this Flask cheat sheet free to use?
Where do the examples come from?
How do I go deeper than a cheat sheet?
Related cheat sheets
Python 3 NumPy pandas Matplotlib Jupyter Notebook FastAPI
Last refreshed 2026-09-27.