Skip to main content

Blueprints & Modularity

Blueprints allow you to organize your Flask application into distinct, reusable components. Instead of defining all routes and handlers on a single Flask application object, you define them on a Blueprint and then register that blueprint with the application.

Creating and Registering Blueprints​

A Blueprint is a way to group related views and other code. It provides the same decorators as the Flask object, such as @route and @before_request, because both classes inherit from flask.sansio.scaffold.Scaffold.

In a typical application factory, you create blueprints in separate modules and register them in the create_app function:

# flaskr/auth.py
from flask import Blueprint

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

@bp.route("/register", methods=("GET", "POST"))
def register():
# ... implementation ...
return "Register"

# flaskr/__init__.py
from flask import Flask
from . import auth

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

When you register a blueprint, Flask records the operations you performed on it (like adding routes) and replays them on the application object.

Configuration and Precedence​

Blueprints support several configuration options that affect how their routes and resources are handled:

  • url_prefix: Prepended to all routes in the blueprint. If the blueprint has @bp.route("/login") and url_prefix="/auth", the final URL is /auth/login.
  • subdomain: Routes in the blueprint will match this subdomain by default.
  • template_folder: A folder containing templates relative to the blueprint's root path.
  • static_folder: A folder containing static files relative to the blueprint's root path.

Template Precedence​

Blueprint templates are added to the application's template search path. However, the application's own templates folder always takes precedence. If a template exists in both the app's folder and the blueprint's folder, Flask will use the one from the app.

Nested Blueprints​

Flask supports nesting blueprints to create hierarchical structures. You can register a blueprint on another blueprint using Blueprint.register_blueprint.

When blueprints are nested, their url_prefix and subdomain are concatenated.

parent = Blueprint("parent", __name__, url_prefix="/parent")
child = Blueprint("child", __name__, url_prefix="/child")

@child.route("/leaf")
def leaf():
return "I am at /parent/child/leaf"

parent.register_blueprint(child)
app.register_blueprint(parent)

Internally, Blueprint.register (in src/flask/sansio/blueprints.py) handles this by updating the url_prefix and name_prefix before recursively calling register on the child blueprints:

# From src/flask/sansio/blueprints.py
if state.url_prefix is not None and bp_url_prefix is not None:
bp_options["url_prefix"] = (
state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/")
)

Application-Wide vs. Blueprint-Specific Hooks​

Blueprints provide two sets of decorators for request lifecycle hooks and error handling:

  1. Blueprint-specific: Decorators like @bp.before_request or @bp.errorhandler only trigger for requests handled by that specific blueprint.
  2. Application-wide: Decorators like @bp.before_app_request, @bp.after_app_request, and @bp.app_errorhandler trigger for every request to the application, regardless of which blueprint (or the app itself) handles it.

These "app" variants are implemented using record_once to ensure they are only registered on the Flask object once, even if the blueprint is registered multiple times.

Internal Mechanism: Deferred Setup​

Because a Blueprint doesn't have an application object when you define routes, it uses a "deferred" setup mechanism. When you call a method like @bp.route, it calls self.record():

# From src/flask/sansio/blueprints.py
@setupmethod
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
self.record(lambda s: s.add_url_rule(rule, endpoint, view_func, **options))

The record method appends the function to self.deferred_functions. When app.register_blueprint(bp) is called, Flask creates a BlueprintSetupState object and passes it to every function in deferred_functions.

The BlueprintSetupState (found in src/flask/sansio/blueprints.py) is a temporary object that holds the registration options (like url_prefix) and provides a helper add_url_rule that automatically prefixes the endpoint with the blueprint's name.

Pitfalls and Restrictions​

Unique Names​

Every blueprint registered on an application must have a unique name. If you try to register two blueprints with the same name, or the same blueprint twice without providing a different name, Flask raises a ValueError.

bp = Blueprint("bp", __name__)
app.register_blueprint(bp)
app.register_blueprint(bp, name="second_copy") # OK: unique name provided

Setup Finished Restriction​

Once a blueprint has been registered on an application, you can no longer call setup methods (like @route or @before_request) on it. Doing so will raise an AssertionError. This is enforced by the @setupmethod decorator which checks the _got_registered_once flag.

No Dots in Names​

Blueprint names and endpoint names cannot contain dots (.). Flask uses the dot as a separator between the blueprint name and the endpoint name (e.g., auth.login) for URL generation with url_for. Passing a name with a dot to the Blueprint constructor will raise a ValueError.