Blueprints

Blueprints organize routes, templates, static files, and error handlers by feature module — the key tool for moving a Flask project from a single file to an engineered structure. A blueprint is not an application by itself; its contents take effect only when registered, so the same blueprint can be reused across multiple app instances.

Defining a Blueprint

# app/blog/views.py
from flask import Blueprint, render_template

bp = Blueprint("blog", __name__, url_prefix="/blog")

@bp.get("/")
def list_posts():
    return render_template("blog/list.html")

@bp.get("/<int:post_id>")
def post_detail(post_id):
    return render_template("blog/detail.html", post_id=post_id)

The first argument "blog" is the blueprint name, which becomes the endpoint namespace; url_prefix prepends /blog to all routes in the blueprint.

Registering Blueprints

# app/__init__.py
from flask import Flask

def create_app():
    app = Flask(__name__)

    from .blog.views import bp as blog_bp
    from .auth.views import bp as auth_bp
    app.register_blueprint(blog_bp)
    app.register_blueprint(auth_bp, url_prefix="/auth")  # Prefix can also be set at registration

    return app

Importing and registering inside the factory function (lazy imports) avoids circular import problems.

Endpoint Namespaces

Blueprint endpoints are named blueprint_name.view_name:

url_for("blog.list_posts")              # /blog/
url_for("blog.post_detail", post_id=3)  # /blog/3
url_for(".post_detail", post_id=3)      # Relative form usable inside the blueprint

Blueprint Templates and Static Files

bp = Blueprint(
    "blog", __name__,
    template_folder="templates",     # app/blog/templates/
    static_folder="static",          # app/blog/static/
    static_url_path="/blog-static",
)

Blueprint template folders have lower priority than the app-wide templates/. The convention is to nest a same-named subdirectory inside the blueprint's template folder (templates/blog/list.html) to avoid name collisions.

Blueprint-Level Hooks and Error Handlers

@bp.before_request           # Runs only for this blueprint's requests
def require_login():
    if "uid" not in session:
        return redirect(url_for("auth.login"))

@bp.errorhandler(ValueError) # Catches only exceptions raised in this blueprint's views
def bad_value(e):
    return {"error": str(e)}, 400

@bp.before_app_request       # app_ prefix: applies to the whole application
def load_current_user():
    g.user = ...

Note: errors like 404 occur during route matching before any blueprint is involved, so only application-level errorhandlers catch them.

Nested Blueprints

Flask 2.0+ supports nesting blueprints — useful for versioned APIs:

api = Blueprint("api", __name__, url_prefix="/api")
v1 = Blueprint("v1", __name__, url_prefix="/v1")

api.register_blueprint(v1)
app.register_blueprint(api)
# Endpoints: api.v1.xxx, URLs: /api/v1/...

Organization Advice

  • Split blueprints by business domain (auth, blog, admin, api), not by technical layer.
  • Give each blueprint its own package containing views.py, models.py, forms.py, etc.
  • Keep blueprints free of dependencies on the app object (never from app import app) — rely on current_app instead.