Skip to main content

Blueprints & Modular Design

Blueprints are a modular way to organize Flask applications by grouping related views, templates, and static files. They allow developers to define application components without requiring an immediate application object, making them essential for building scalable and maintainable architectures.

In this codebase, the Blueprint class (found in src/flask/blueprints.py and src/flask/sansio/blueprints.py) inherits from Scaffold, providing it with the same routing and configuration capabilities as the main Flask application object.

Core Concepts

The Blueprint Class

A Blueprint is defined by a unique name and an import_name (usually __name__). The name is used to prefix all endpoints defined within the blueprint, which prevents naming collisions across different modules.

# From examples/tutorial/flaskr/auth.py
from flask import Blueprint

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

When a blueprint is initialized, it records operations (like route registrations) in a list called deferred_functions. These operations are only applied to the application when the blueprint is registered.

Registration and Setup State

The registration process is handled by app.register_blueprint(). Internally, this method calls the blueprint's register method, which creates a BlueprintSetupState object. This state object carries configuration options—such as url_prefix and subdomain—and applies the deferred functions to the Flask application instance.

# From src/flask/sansio/blueprints.py
class BlueprintSetupState:
def __init__(self, blueprint, app, options, first_registration):
self.app = app
self.blueprint = blueprint
self.options = options
self.first_registration = first_registration
# ... resolves url_prefix and subdomain ...

Modular Application Structure

Application Factories

Blueprints are typically registered within an application factory function. This pattern allows for creating multiple instances of the same application with different configurations.

# From examples/tutorial/flaskr/__init__.py
def create_app(test_config=None):
app = Flask(__name__, instance_relative_config=True)
# ... configuration ...

from . import auth, blog
app.register_blueprint(auth.bp)
app.register_blueprint(blog.bp)

return app

Middleware and Handlers

Blueprints support both local and global middleware.

  • @bp.before_request: Runs only before requests handled by this blueprint.
  • @bp.before_app_request: Runs before every request in the entire application, regardless of which blueprint handles it.

Example of a global handler defined within a blueprint:

# From examples/tutorial/flaskr/auth.py
@bp.before_app_request
def load_logged_in_user():
user_id = session.get("user_id")
if user_id is None:
g.user = None
else:
g.user = get_db().execute(
"SELECT * FROM user WHERE id = ?", (user_id,)
).fetchone()

Advanced Modularization

Nesting Blueprints

Blueprints can be registered on other blueprints. This allows for hierarchical URL structures where prefixes and subdomains are applied cumulatively.

# From tests/test_blueprints.py
parent = Blueprint("parent", __name__)
child = Blueprint("child", __name__)
grandchild = Blueprint("grandchild", __name__)

child.register_blueprint(grandchild, url_prefix="/grandchild")
parent.register_blueprint(child, url_prefix="/child")
app.register_blueprint(parent, url_prefix="/parent")
# Resulting URL for a route in grandchild: /parent/child/grandchild/route

Resource Management

Each blueprint can have its own static_folder and template_folder.

  • Templates: Blueprint templates are added to the application's template search path but have lower precedence than the main application's templates.
  • Static Files: If a static_folder is provided, a route is automatically registered to serve files from that directory.
# From src/flask/sansio/blueprints.py
if self.has_static_folder:
state.add_url_rule(
f"{self.static_url_path}/<path:filename>",
view_func=self.send_static_file,
endpoint="static",
)

Constraints and Best Practices

  1. Naming Restrictions: Blueprint names cannot contain dots (.). This is because dots are used as delimiters for endpoint names (e.g., auth.login).
  2. Registration Lock: Once a blueprint is registered with an application, its _got_registered_once flag is set to True. Attempting to call setup methods like @bp.route after registration will raise an AssertionError.
  3. URL Prefixing: Using url_prefix in the Blueprint constructor or during register_blueprint is the recommended way to namespace routes and avoid collisions.
  4. Endpoint Resolution: When using url_for within a blueprint, you can use a leading dot (e.g., url_for(".login")) to refer to an endpoint within the same blueprint. Otherwise, the full name (e.g., url_for("auth.login")) must be used.