Skip to main content

Building a Blueprint with Custom Templates

When you build large Flask applications, you often want to group related views and templates into a Blueprint. While Flask allows each blueprint to define its own template directory, all templates are ultimately managed by a single global environment. This tutorial walks you through organizing templates within a blueprint and ensuring they are correctly discovered by Flask's DispatchingJinjaLoader.

Prerequisites

  • A basic Flask application structure.
  • A directory for your blueprint (e.g., project/auth/).

Step 1: Define the Blueprint with a Template Folder

When you create a Blueprint, you can specify a template_folder. This path is relative to the blueprint's directory.

from flask import Blueprint

# Define a blueprint for authentication
auth_bp = Blueprint(
"auth",
__name__,
template_folder="templates"
)

By setting template_folder="templates", Flask will look for templates in project/auth/templates/ when this blueprint is registered.

Step 2: Namespace Your Templates

Flask's DispatchingJinjaLoader searches for templates in a specific order:

  1. The application's main template_folder.
  2. Each registered blueprint's template_folder, in the order they were registered.

Because the first match wins, a template named index.html in your blueprint will be ignored if the main application also has an index.html. To prevent this, always namespace your blueprint templates by placing them in a subfolder named after the blueprint.

Correct Directory Structure:

project/
├── app.py
└── auth/
├── __init__.py
└── templates/
└── auth/ <-- Namespace folder
└── login.html

Step 3: Register the Blueprint

In your main application file, register the blueprint. The order of registration matters if multiple blueprints contain templates with the same path.

from flask import Flask
from .auth import auth_bp

app = Flask(__name__)
app.register_blueprint(auth_bp)

When app.register_blueprint is called, Flask updates its internal DispatchingJinjaLoader to include the blueprint's loader in its search path.

Step 4: Render the Template

Use render_template within your blueprint views. Because you namespaced the template in Step 2, you must include the namespace in the path.

@auth_bp.route("/login")
def login():
# Flask finds this via DispatchingJinjaLoader
return render_template("auth/login.html")

Step 5: Debug Template Discovery

If Flask cannot find your template or is loading the wrong one, you can enable EXPLAIN_TEMPLATE_LOADING. This configuration tells DispatchingJinjaLoader to log every attempt it makes to find a template.

app.config["EXPLAIN_TEMPLATE_LOADING"] = True

When this is enabled, a failed render_template call will output a detailed report to the console showing which loaders were checked and which paths were attempted:

Locating template "auth/login.html":
- Try loading from "app" (FileSystemLoader):
- project/templates/auth/login.html -> [NOT FOUND]
- Try loading from "auth" (FileSystemLoader):
- project/auth/templates/auth/login.html -> [FOUND]

How it Works

Flask uses the flask.templating.Environment class to manage Jinja. During initialization, this environment sets up a DispatchingJinjaLoader.

The DispatchingJinjaLoader._iter_loaders method is responsible for the search order. It yields the application's loader first, followed by the loaders for every blueprint:

# Simplified logic from flask/templating.py
def _iter_loaders(self, template):
loader = self.app.jinja_loader
if loader is not None:
yield self.app, loader

for blueprint in self.app.iter_blueprints():
loader = blueprint.jinja_loader
if loader is not None:
yield blueprint, loader

This implementation ensures that the application can always override blueprint templates by simply placing a file with the same name in the main templates/ directory.