cheat sheet

flask

Package-level reference for Flask on PyPI — install variants, version policy, extension ecosystem, and alternatives.

#pip#package#web#apiupdated 05-31-2026

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

bash
pip install flask

Output: (none — exits 0 on success)

bash
uv add flask

Output: dependency resolved + added to pyproject.toml

bash
poetry add flask

Output: updated lockfile + virtualenv install

bash
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.x series (as of mid-2026). Flask 3.0 released in late 2023; the 2.x series before it ran for several years.
  • Supports Python 3.8+ on recent releases. The 3.x line 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] — installs asgiref so async def view handlers run on a per-request thread.
  • flask[dotenv] — installs python-dotenv so flask run auto-loads .env / .flaskenv files.

Core dependencies pulled in automatically:

  • werkzeug — WSGI utilities, dev server, request/response objects
  • jinja2 — template engine
  • click — command-line interface (flask CLI)
  • itsdangerous — signed cookies and session tokens
  • blinker — signal/event support

Common companion packages typically installed alongside:

  • gunicorn / waitress / uwsgi — production WSGI servers
  • flask-sqlalchemy — SQLAlchemy integration with app-context scoping
  • flask-migrate — Alembic migrations wired into the Flask CLI
  • flask-login — session-based user auth
  • flask-wtf — WTForms with CSRF protection
  • flask-cors — CORS handling
  • flask-restful / flask-smorest — REST API helpers
  • flask-caching — view and function caching
  • python-dotenv.env loading

Alternatives

PackageTrade-off
fastapiAsync, type-driven, auto OpenAPI. Use for new typed REST APIs.
djangoBatteries included — ORM, admin, auth. Use for full-stack apps with templates.
litestarAsync, strict typing, lower overhead than FastAPI. Use for high-throughput APIs.
bottleSingle-file microframework, no extensions ecosystem. Use only for very small scripts.
quartFlask-API-compatible but async. Use when migrating a Flask codebase to async.
starletteLower-level ASGI toolkit. Use when you want routing without WSGI.

Common gotchas

  1. flask run dev server is not for production. Single-threaded, no security hardening, prints a warning at startup. Use gunicorn, waitress, or uwsgi for deployments.
  2. async def views run on a thread, not the event loop. Even with flask[async], Flask is still WSGI — each async view is dispatched to a thread via asgiref.AsyncToSync. No throughput win over sync views; use FastAPI or Litestar if you actually need async I/O.
  3. App context vs request context. current_app, g, request, and session are thread-local proxies tied to specific contexts. Accessing them outside a request (e.g. in a background thread) raises RuntimeError: Working outside of application context.
  4. Blueprints register at import time. Circular imports between blueprints and the app factory are a common foot-gun — use lazy imports inside create_app().
  5. 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).
  6. SECRET_KEY is required for sessions. Forgetting to set it produces a RuntimeError on first session access. Never commit a hardcoded value — load from env.
  7. Werkzeug 2.x3.x bump. Flask 3.0 pulls in Werkzeug 3.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.

bash
# 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.

bash
# 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.

bash
# 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.

python
# 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:

python
# 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
python
# 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
python
# 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+):

python
# 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_KEY must be long, random, and per-environment. Generate with secrets.token_urlsafe(64); never reuse across environments; rotate periodically. Sessions and signed URLs are forgeable without it.
  • CSRF. Use flask-wtf or flask-seasurf for form-based apps. AJAX endpoints need an explicit token-header check; do NOT rely on same-site cookies alone.
  • CORS. flask-cors — never combine origins="*" with supports_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-talisman for security headers — CSP, HSTS, X-Frame-Options, Referrer-Policy. Highly recommended for any browser-facing app.
  • SEND_FILE_MAX_AGE_DEFAULT controls cache lifetime for send_from_directory. Default is 12 hours; set tighter for sensitive assets.
  • ProxyFix with explicit counts only. Setting x_for=0 accepts 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 --debug in 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.

python
# 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.

python
# 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.

python
# 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.

python
# 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 / SymptomLikely causeFix
RuntimeError: Working outside of application contextAccessing current_app from a thread/taskWrap in with app.app_context(): or use the Celery FlaskTask pattern.
RuntimeError: Working outside of request contextAccessing request/g/session outside a requestRestructure so the call happens inside a view, or use app.test_request_context().
KeyError: 'SECRET_KEY' on session useSECRET_KEY not setSet via env: export FLASK_SECRET_KEY=...; load via app.config.from_prefixed_env().
RuntimeError: The session is unavailable because no secret key was setSame as aboveSame fix; double-check the factory loads config before any blueprint imports session.
werkzeug.routing.BuildErrorurl_for('view_name') with wrong endpointUse the blueprint-prefixed name: url_for('auth.login') not url_for('login').
flask run not auto-reloadingflask[dotenv] missing or FLASK_DEBUG=1 not setpip install "flask[dotenv]" and set FLASK_DEBUG=1 in .flaskenv.
404 on a registered blueprint routeWrong url_prefix or factory orderVerify register_blueprint(bp, url_prefix=...) runs before requests.
TypeError: The view function did not return a valid responseReturning None or a non-ResponseReturn a string, tuple (body, status), dict (auto-JSON in 2.x+), or Response.
Slow first request after deployApp init in module scopeMove heavy init into the factory; gunicorn --preload to fork after init.
Memory grows in long-running workerDB sessions not closedUse teardown_request to close sessions; switch to gthread worker class.

See also