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.