Understanding Blueprints

Difficulty: Intermediate

Blueprints are Flask's mechanism for organizing a large application into smaller, reusable modules. A Blueprint is like a mini-application that defines routes, templates, static files, and error handlers, but cannot run on its own. It must be registered with a Flask application to become active.

As your Flask application grows, keeping everything in a single file becomes unmanageable. Blueprints solve this by letting you split your application into logical modules. A typical application might have blueprints for authentication (login, register, logout), the main site (home, about, contact), an admin panel, and an API. Each blueprint is self-contained with its own routes and templates.

Creating a Blueprint is similar to creating a Flask app. You instantiate Blueprint with a name and the import name (usually __name__). Then you define routes using @bp.route() instead of @app.route(). The blueprint is registered with the application using app.register_blueprint().

The url_prefix parameter is powerful. When you register a blueprint with url_prefix='/api', all routes defined in that blueprint are prefixed. A route defined as @bp.route('/users') becomes accessible at '/api/users'. This keeps URL namespaces clean and prevents conflicts between blueprints.

Blueprints support their own template and static folders. By default, they use the application's template and static folders. You can set template_folder and static_folder when creating the Blueprint to use blueprint-specific resources. Templates in blueprint folders take lower precedence than the application's template folder, so the app can override blueprint templates.

Blueprints can have their own error handlers with @bp.errorhandler() and before/after request hooks with @bp.before_request() and @bp.after_request(). These hooks only run for requests handled by the blueprint, not for all application requests.

When using url_for() with blueprints, you prefix the endpoint with the blueprint name: url_for('auth.login') instead of url_for('login'). Within the same blueprint, you can use a dot prefix: url_for('.login'). This namespacing prevents endpoint name conflicts between blueprints.

Code examples

Creating and Registering a Blueprint

# blueprints/auth.py
from flask import Blueprint, request, redirect, url_for, session

auth_bp = Blueprint('auth', __name__)

@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        session['user'] = request.form['username']
        return redirect(url_for('main.index'))
    return '<form method="POST"><input name="username"><button>Login</button></form>'

@auth_bp.route('/logout')
def logout():
    session.pop('user', None)
    return redirect(url_for('main.index'))

# app.py
from flask import Flask
from blueprints.auth import auth_bp

app = Flask(__name__)
app.secret_key = 'secret'
app.register_blueprint(auth_bp, url_prefix='/auth')
# Routes: /auth/login, /auth/logout

A Blueprint is created in a separate file. Routes use @auth_bp.route() instead of @app.route(). The blueprint is registered with a url_prefix, so /login becomes /auth/login. Use url_for('auth.login') to reference its routes.

Multiple Blueprints

# blueprints/main.py
from flask import Blueprint

main_bp = Blueprint('main', __name__)

@main_bp.route('/')
def index():
    return 'Home Page'

# blueprints/api.py
from flask import Blueprint, jsonify

api_bp = Blueprint('api', __name__)

@api_bp.route('/users')
def users():
    return jsonify([{'id': 1, 'name': 'Alice'}])

# app.py
from flask import Flask
from blueprints.main import main_bp
from blueprints.api import api_bp

app = Flask(__name__)
app.register_blueprint(main_bp)
app.register_blueprint(api_bp, url_prefix='/api')

# Routes:
# /          -> main.index
# /api/users -> api.users

You can register multiple blueprints. The main blueprint has no prefix (serves the root), while the API blueprint is prefixed with /api. Each blueprint is a self-contained module.

Blueprint-Specific Templates and Static Files

# blueprints/admin/__init__.py
from flask import Blueprint

admin_bp = Blueprint(
    'admin',
    __name__,
    template_folder='templates',  # blueprints/admin/templates/
    static_folder='static',        # blueprints/admin/static/
    static_url_path='/admin/static'
)

@admin_bp.route('/')
def dashboard():
    # Looks for admin/dashboard.html in:
    # 1. app/templates/admin/dashboard.html (app overrides)
    # 2. blueprints/admin/templates/admin/dashboard.html
    from flask import render_template
    return render_template('admin/dashboard.html')

# File structure:
# blueprints/admin/
#     __init__.py
#     templates/admin/
#         dashboard.html
#     static/
#         css/admin.css

Blueprints can have their own templates and static directories. Template namespacing (admin/ prefix) prevents conflicts. The application's templates can override blueprint templates.

Blueprint Hooks and Error Handlers

from flask import Blueprint, jsonify, g
import time

api_bp = Blueprint('api', __name__)

@api_bp.before_request
def before_api_request():
    """Runs before every request in this blueprint."""
    g.start_time = time.time()

@api_bp.after_request
def after_api_request(response):
    """Add timing header to API responses."""
    elapsed = time.time() - g.start_time
    response.headers['X-Response-Time'] = f'{elapsed:.3f}s'
    return response

@api_bp.errorhandler(404)
def api_not_found(error):
    """Only handles 404s within the api blueprint."""
    return jsonify({'error': 'Resource not found'}), 404

@api_bp.route('/data')
def get_data():
    return jsonify({'value': 42})

Blueprint hooks (before_request, after_request) only affect requests handled by that blueprint. Blueprint error handlers also only handle errors within the blueprint. This lets you customize behavior per module.

Key points

Concepts covered

Blueprint, Modular Apps, Register Blueprint, URL Prefix, Namespace