Blueprints and the application factory
Split a growing app into blueprints, build the app inside create_app() for testability, and initialise extensions without circular imports.
Blueprints
A blueprint is a collection of routes, templates and handlers that is registered on an application later. It is not an application: it has no config, no request handling of its own, and it cannot be run. That distinction is what makes it useful β the same blueprint can be mounted on a test app, mounted twice with different prefixes, or not mounted at all.
# app/shop/views.py
from flask import Blueprint, render_template, abort
from .models import Product
bp = Blueprint(
"shop", __name__,
url_prefix="/shop",
template_folder="templates", # app/shop/templates/shop/detail.html
static_folder="static", # served under /shop/static
)
@bp.get("/")
def index():
return render_template("shop/index.html", products=Product.all())
@bp.get("/<int:product_id>")
def detail(product_id):
product = Product.get(product_id)
if product is None:
abort(404)
return render_template("shop/detail.html", product=product)
@bp.before_request
def require_open_shop():
if not current_app.config["SHOP_OPEN"] and request.endpoint != "shop.index":
return redirect(url_for("shop.index"))| Blueprint feature | Behaviour |
|---|---|
url_prefix | Prepended to every rule in the blueprint |
| Endpoint name | shop.detail β the blueprint name, a dot, then the function name |
template_folder | Added to the template search path, not a namespace |
static_folder | Served under <url_prefix>/static when a prefix is set |
before_request / after_request | Run only for requests that match this blueprint |
errorhandler | Catches errors raised inside this blueprint's routes |
| Nested blueprints | A blueprint registered on a blueprint, for deep prefixes |
The application factory
# app/__init__.py
from flask import Flask
from .extensions import db, migrate, login_manager
def create_app(config_object="app.config.DevelopmentConfig"):
app = Flask(__name__, instance_relative_config=True)
app.config.from_object(config_object)
db.init_app(app)
migrate.init_app(app, db)
login_manager.init_app(app)
from .shop.views import bp as shop_bp
from .blog.views import bp as blog_bp
app.register_blueprint(shop_bp) # /shop/...
app.register_blueprint(blog_bp, url_prefix="/blog")
@app.get("/healthz")
def healthz():
return {"ok": True}
return app
# run.py (development)
from app import create_app
app = create_app()
if __name__ == "__main__":
app.run(debug=True)
# production β gunicorn resolves the factory for you
# gunicorn "app:create_app()"- The import of the blueprint modules happens inside the factory. That is what breaks the circular dependency: views import
dbfrom the extension module, and the extension module never imports the app. - Everything that needs an app object happens after
create_app()is called β configuration reads, extension registration, CLI commands, error handlers. Module-level code that touchescurrent_appfails at import. instance_relative_config=Trueletsapp.config.from_pyfile("config.py", silent=True)read a file outside version control, which is where machine-specific settings belong.- Tests build a fresh app with the testing config. Two independent apps in one process only works because nothing is global.
Extensions, contexts and CLI commands
# app/extensions.py β one module, no app import
from flask_login import LoginManager
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()
login_manager.login_view = "auth.login"
# scripts and CLI work: you need an application context
from app import create_app
application = create_app()
with application.app_context():
print(db.session.execute(db.select(db.func.now())).scalar())
# register a command on the app, from anywhere in the package
import click
@app.cli.command("seed")
@click.option("--count", default=10)
def seed(count):
"""Create demo rows."""
create_demo_data(count)
click.echo(f"seeded {count} rows")
# shell context: names preloaded in "flask shell"
@app.shell_context_processor
def shell_context():
return {"db": db, "User": User}- An extension object is created once and bound to an app with
init_app. Callinginit_apptwice on the same app raises; creating two apps in one process is exactly what the pattern is for. - The app context is not optional.
current_app,g,url_forwithout a request, and any database query all need one β inside a view Flask pushes it for you, in a script you push it yourself. - Blueprint-scoped CLI commands are registered by the blueprint's own
@bp.cli.commandand appear once the blueprint is registered.
β οΈ
The two failure modes of this pattern are opposite and equally confusing. Module-level work β a query, a
current_app.config read, an app.route call β raises RuntimeError: Working outside of application context the moment the package is imported, before any request exists. The reverse mistake is creating the app at import time in a module that views also import, which produces a circular import or two Flask apps living in one process, each with its own extension registrations. Keep exactly one create_app() call site: the WSGI entry point, or a fixture in tests.FAQ
Blueprint or just more modules?
A blueprint when the group owns routes, templates and handlers that belong together and can be mounted at a prefix. A plain module of helper functions needs no blueprint β blueprints are for the HTTP surface.
How do I reuse one blueprint under two prefixes?
Register it twice with different
name= and url_prefix= values, for example app.register_blueprint(bp, name="admin_shop", url_prefix="/admin/shop"). Endpoint names then differ too, so url_for resolves to the right mount.Related
Configuration and project structure Databases with Flask-SQLAlchemy
Last refreshed 2026-09-18.