cheat sheet
flask
Package-level reference for Flask on PyPI — install variants, version policy, extension ecosystem, and alternatives.
flask
What it is
flask is a synchronous WSGI microframework created by Armin Ronacher in 2010 and now maintained by the Pallets Projects organization. It bundles routing, request/response handling, and Jinja2 templating; everything else — ORM, forms, auth, admin — is opt-in via the extension ecosystem.
Reach for Flask when you want full control of the stack, a small surface area, or a familiar synchronous codebase. Reach for FastAPI/Litestar when you need native async or auto-generated OpenAPI; Django when you want batteries included.
Install
pip install flask
Output: (none — exits 0 on success)
uv add flask
Output: dependency resolved + added to pyproject.toml
poetry add flask
Output: updated lockfile + virtualenv install
pip install "flask[async]" # async view support via asgiref
Output: Flask plus asgiref for async def views inside the WSGI app
Versioning & Python support
- Current line is the
3.xseries (as of mid-2026). Flask3.0released in late 2023; the2.xseries before it ran for several years. - Supports Python 3.8+ on recent releases. The
3.xline dropped Python 3.7 and earlier. - Loose semver — major bumps coincide with Pallets-wide dependency floor changes (Werkzeug, Jinja2, click).
- Long-term maintenance focus: security and small ergonomic improvements rather than new features.
Package metadata
- Maintainer: Pallets Projects (David Lord et al.)
- Project home: github.com/pallets/flask
- Docs: flask.palletsprojects.com
- PyPI: pypi.org/project/flask
- License: BSD-3-Clause
- Governance: Pallets Projects (community-led with corporate sponsors)
- First released: 2010
- Downloads: ~100M+/month — still one of the top web frameworks on PyPI
Optional dependencies & extras
flask[async]— installsasgirefsoasync defview handlers run on a per-request thread.flask[dotenv]— installspython-dotenvsoflask runauto-loads.env/.flaskenvfiles.
Core dependencies pulled in automatically:
werkzeug— WSGI utilities, dev server, request/response objectsjinja2— template engineclick— command-line interface (flaskCLI)itsdangerous— signed cookies and session tokensblinker— signal/event support
Common companion packages typically installed alongside:
gunicorn/waitress/uwsgi— production WSGI serversflask-sqlalchemy— SQLAlchemy integration with app-context scopingflask-migrate— Alembic migrations wired into the Flask CLIflask-login— session-based user authflask-wtf— WTForms with CSRF protectionflask-cors— CORS handlingflask-restful/flask-smorest— REST API helpersflask-caching— view and function cachingpython-dotenv—.envloading
Alternatives
| Package | Trade-off |
|---|---|
fastapi | Async, type-driven, auto OpenAPI. Use for new typed REST APIs. |
django | Batteries included — ORM, admin, auth. Use for full-stack apps with templates. |
litestar | Async, strict typing, lower overhead than FastAPI. Use for high-throughput APIs. |
bottle | Single-file microframework, no extensions ecosystem. Use only for very small scripts. |
quart | Flask-API-compatible but async. Use when migrating a Flask codebase to async. |
starlette | Lower-level ASGI toolkit. Use when you want routing without WSGI. |
Common gotchas
flask rundev server is not for production. Single-threaded, no security hardening, prints a warning at startup. Usegunicorn,waitress, oruwsgifor deployments.async defviews run on a thread, not the event loop. Even withflask[async], Flask is still WSGI — each async view is dispatched to a thread viaasgiref.AsyncToSync. No throughput win over sync views; use FastAPI or Litestar if you actually need async I/O.- App context vs request context.
current_app,g,request, andsessionare thread-local proxies tied to specific contexts. Accessing them outside a request (e.g. in a background thread) raisesRuntimeError: Working outside of application context. - Blueprints register at import time. Circular imports between blueprints and the app factory are a common foot-gun — use lazy imports inside
create_app(). - No built-in ORM, forms, or auth. Every project that needs these picks a different extension. The ecosystem is fragmented; pick the canonical extension per concern (
flask-sqlalchemy,flask-login,flask-wtf). SECRET_KEYis required for sessions. Forgetting to set it produces aRuntimeErroron first session access. Never commit a hardcoded value — load from env.- Werkzeug
2.x→3.xbump. Flask3.0pulls in Werkzeug3.x, which removed some long-deprecated helpers. Older extensions may break — check extension compatibility before upgrading.
Production deployment
Flask is WSGI — production means picking a WSGI server (gunicorn, waitress, uwsgi) and putting it behind a reverse proxy. Never expose flask run to the internet; its dev server is single-threaded and prints a security banner for a reason.
# gunicorn — most common Linux choice
pip install gunicorn
gunicorn 'myapp:create_app()' \
--workers 4 \
--threads 2 \
--worker-class gthread \
--bind 0.0.0.0:8000 \
--timeout 60 \
--access-logfile - \
--max-requests 1000 \
--max-requests-jitter 50
Output: 4 worker processes × 2 threads each; --max-requests cycles workers to mitigate slow memory leaks.
# waitress — pure-Python, runs on Windows and macOS
pip install waitress
waitress-serve --listen=0.0.0.0:8000 --threads=8 myapp:app
Output: single-process multi-threaded server; good fit for Windows hosts.
# uWSGI — older Linux standard, more knobs
pip install uwsgi
uwsgi --http :8000 --module myapp:app --processes 4 --threads 2 --master --vacuum
Output: master+workers process tree; --vacuum cleans up the socket on exit.
# app factory pattern — required for gunicorn 'module:factory()' invocation
# myapp/__init__.py
from flask import Flask
def create_app(config: dict | None = None) -> Flask:
app = Flask(__name__)
app.config.from_prefixed_env() # FLASK_SECRET_KEY -> SECRET_KEY etc.
if config: app.config.update(config)
from .routes import bp
app.register_blueprint(bp)
return app
Output: importable factory; tests can call create_app(test_config) for an isolated instance.
Reverse-proxy notes: set ProxyFix middleware so request.remote_addr honors X-Forwarded-For. With nginx in front: proxy_set_header X-Forwarded-Proto $scheme; and configure ProxyFix(app.wsgi_app, x_proto=1, x_for=1). Without this, redirects loop and request.is_secure lies.
Version migration guide
The notable break is Flask 2.x → 3.0 which pulled in Werkzeug 3.x and removed several long-deprecated helpers. Older Flask extensions sometimes lag the major bump; check each extension's changelog before upgrading.
Flask 2.x → 3.0:
# Removed/changed APIs
# - flask.json.JSONEncoder / JSONDecoder gone; use app.json.default(o) hook
# - safe_join() moved entirely to werkzeug.utils.safe_join
# - send_file() positional `attachment_filename=` removed; use `download_name=`
# - escape() / Markup re-exported from flask removed; import from markupsafe
# Before (Flask 2.x)
from flask import Flask
from flask.json import JSONEncoder
class MyEncoder(JSONEncoder):
def default(self, o): ...
app = Flask(__name__)
app.json_encoder = MyEncoder
# After (Flask 3.0)
from flask import Flask
from flask.json.provider import DefaultJSONProvider
class MyProvider(DefaultJSONProvider):
def default(self, o): ...
app = Flask(__name__)
app.json = MyProvider(app)
Output: equivalent JSON serialization; the new provider class is the supported extension point.
async def view evolution (Flask 2.0+):
# Requires `pip install "flask[async]"`
@app.get("/slow")
async def slow():
await asyncio.sleep(1)
return {"ok": True}
Output: view runs on a worker thread via asgiref; no real async I/O parallelism — Flask is still WSGI.
Werkzeug 3.x migration impact: most Flask code is unaffected, but custom URL converters using _BoundConverterArgs and signed-cookie-extension code may need updates. Run with PYTHONWARNINGS=error::DeprecationWarning during the upgrade to surface the lingering call sites.
Security considerations
Flask defaults are reasonable but minimal — much of the security model is in the extension layer (flask-wtf, flask-login, flask-talisman). Treat the checklist below as the default for any public-facing Flask app.
SECRET_KEYmust be long, random, and per-environment. Generate withsecrets.token_urlsafe(64); never reuse across environments; rotate periodically. Sessions and signed URLs are forgeable without it.- CSRF. Use
flask-wtforflask-seasurffor form-based apps. AJAX endpoints need an explicit token-header check; do NOT rely on same-site cookies alone. - CORS.
flask-cors— never combineorigins="*"withsupports_credentials=True. - Session cookies. Set
SESSION_COOKIE_SECURE=True,SESSION_COOKIE_HTTPONLY=True,SESSION_COOKIE_SAMESITE="Lax"(or"Strict"). The defaults are NOT secure for HTTPS-only deployments. flask-talismanfor security headers — CSP, HSTS, X-Frame-Options, Referrer-Policy. Highly recommended for any browser-facing app.SEND_FILE_MAX_AGE_DEFAULTcontrols cache lifetime forsend_from_directory. Default is 12 hours; set tighter for sensitive assets.ProxyFixwith explicit counts only. Settingx_for=0accepts all headers (dangerous behind an untrusted LB); set to the exact number of hops between the public client and your app.- Werkzeug dev server has had RCE CVEs. Never run
flask run --debugin production — the debugger console accepts arbitrary code. Bind to localhost only. app.config.from_object()with untrusted input is RCE. Only load config from controlled module paths.
Real-world recipes
Practical patterns for shipping Flask apps — app factory, blueprint composition, background work, and dependency injection. Companion python/flask.md covers route handlers and request basics; here we focus on structure and integration.
# Recipe 1 — App factory + blueprints + per-env config
# myapp/__init__.py
from flask import Flask
from .extensions import db, login
from .blueprints.auth import auth_bp
from .blueprints.api import api_bp
def create_app(env: str = "production") -> Flask:
app = Flask(__name__, instance_relative_config=True)
app.config.from_object(f"myapp.config.{env.capitalize()}Config")
db.init_app(app)
login.init_app(app)
app.register_blueprint(auth_bp, url_prefix="/auth")
app.register_blueprint(api_bp, url_prefix="/api/v1")
return app
Output: factory returns a fresh app per call; tests get isolation, gunicorn calls create_app() directly.
# Recipe 2 — Background task via Celery (Flask 3.x integration)
# tasks.py
from celery import Celery, Task
def celery_init_app(app):
class FlaskTask(Task):
def __call__(self, *a, **kw):
with app.app_context():
return self.run(*a, **kw)
celery = Celery(app.name, task_cls=FlaskTask)
celery.config_from_object(app.config["CELERY"])
app.extensions["celery"] = celery
return celery
Output: Celery worker tasks run inside the Flask app context — current_app and db.session work.
# Recipe 3 — Manual dependency wiring via app.extensions
# extensions.py
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
db = SQLAlchemy()
login = LoginManager()
login.login_view = "auth.login"
Output: shared singletons; db.init_app(app) and login.init_app(app) connect them to a specific app.
# Recipe 4 — Request-scoped DB session with `g`
from flask import g
@app.before_request
def open_session():
g.session = SessionLocal()
@app.teardown_request
def close_session(exc):
sess = g.pop("session", None)
if sess is not None: sess.close()
Output: every request gets a fresh DB session, automatically closed on response or exception.
Troubleshooting common errors
| Error / Symptom | Likely cause | Fix |
|---|---|---|
RuntimeError: Working outside of application context | Accessing current_app from a thread/task | Wrap in with app.app_context(): or use the Celery FlaskTask pattern. |
RuntimeError: Working outside of request context | Accessing request/g/session outside a request | Restructure so the call happens inside a view, or use app.test_request_context(). |
KeyError: 'SECRET_KEY' on session use | SECRET_KEY not set | Set via env: export FLASK_SECRET_KEY=...; load via app.config.from_prefixed_env(). |
RuntimeError: The session is unavailable because no secret key was set | Same as above | Same fix; double-check the factory loads config before any blueprint imports session. |
werkzeug.routing.BuildError | url_for('view_name') with wrong endpoint | Use the blueprint-prefixed name: url_for('auth.login') not url_for('login'). |
flask run not auto-reloading | flask[dotenv] missing or FLASK_DEBUG=1 not set | pip install "flask[dotenv]" and set FLASK_DEBUG=1 in .flaskenv. |
| 404 on a registered blueprint route | Wrong url_prefix or factory order | Verify register_blueprint(bp, url_prefix=...) runs before requests. |
TypeError: The view function did not return a valid response | Returning None or a non-Response | Return a string, tuple (body, status), dict (auto-JSON in 2.x+), or Response. |
| Slow first request after deploy | App init in module scope | Move heavy init into the factory; gunicorn --preload to fork after init. |
| Memory grows in long-running worker | DB sessions not closed | Use teardown_request to close sessions; switch to gthread worker class. |
See also
- Python: flask — API tutorial, routes, blueprints, examples
- Concept: API — REST design fundamentals
- Concept: HTTP — protocol fundamentals
- Packages: pip-fastapi — the typed-async alternative