Skip to content
Rochester, NY design, systems, study tools, and experiments Built in public

Flask blueprints: modular routing without a microservice

Blueprints group related routes, templates, and static files into a self-contained module that registers onto the main Flask app. They give you modularity without splitting the deployment.

web python flask web architecture 2026-04-15

A Flask blueprint is a way to organize a chunk of your app — a set of routes, templates, and static files — into its own module that the main app then registers. The runtime is still one process, one Flask app, one deployment. The split is purely organizational.

Why blueprints over plain routes

In a small app, defining routes directly on the Flask() instance is fine. Once you have more than a handful, three problems show up:

  1. One huge app.py with every route, harder to navigate.
  2. Circular imports when route files need shared models or extensions.
  3. No URL prefix grouping — you have to spell out /admin/... in front of every admin route.

Blueprints solve all three. Each blueprint is a Blueprint() object that collects routes the same way Flask() does, then gets attached at registration time.

Minimal example

# blueprints/blog.py
from flask import Blueprint, render_template

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

@blog_bp.route("/")
def index():
    return render_template("blog/index.html")

@blog_bp.route("/<slug>")
def post(slug):
    return render_template("blog/post.html", slug=slug)
# app.py
from flask import Flask
from blueprints.blog import blog_bp

def create_app():
    app = Flask(__name__)
    app.register_blueprint(blog_bp)
    return app

Routes are now /blog/ and /blog/<slug> — the prefix is applied once at registration.

Useful blueprint features

  • url_prefix — applied to every route in the blueprint.
  • template_folder / static_folder — each blueprint can carry its own templates and static assets.
  • url_for("blog.post", slug="hello") — blueprint name is part of the endpoint, so url_for calls stay namespaced and can't collide.
  • Per-blueprint before_request, after_request, errorhandler — scope middleware to just the routes that need it (e.g., admin auth checks only on the admin blueprint).

When NOT to use a blueprint

  • A single route that doesn't fit any group — leave it on the main app.
  • You actually need an independent deployment with its own scaling, secrets, or release cadence — that's a separate service, not a blueprint.

Common pitfall

If two blueprints define a route with the same endpoint name (the function name, not the URL), the second registration silently overrides the first. Naming the function with a verb specific to the blueprint (blog_index, not index) avoids accidental collisions.