Skip to main content

The Template Resolution Process

Flask uses a specialized template loading system to support its modular architecture. Instead of a simple file-system lookup, Flask employs a "dispatching" mechanism that coordinates template discovery across the main application and all registered blueprints. This process is managed by the DispatchingJinjaLoader class in flask.templating.

The Dispatching Mechanism

The DispatchingJinjaLoader does not load templates directly from the disk. Instead, it acts as a coordinator that iterates through the individual loaders of the application and its blueprints. When a template is requested via render_template, the loader searches for the first match according to a strict priority order.

Resolution Priority

The search order is determined by the _iter_loaders method. Flask prioritizes the application's own templates over those provided by blueprints:

  1. The Application Loader: Flask first checks the template_folder configured on the Flask application instance.
  2. Blueprint Loaders: If the template is not found in the application folder, Flask iterates through all registered blueprints in the order they were registered.
def _iter_loaders(self, template: str) -> t.Iterator[tuple[Scaffold, BaseLoader]]:
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 design allows application developers to override templates provided by blueprints. For example, if a blueprint named auth provides a template at auth/login.html, the application can provide its own version by placing a file at the same path in its own templates directory.

Fast vs. Explained Loading

The DispatchingJinjaLoader implements two modes for finding template sources: a "fast" mode for production performance and an "explained" mode for debugging.

Fast Resolution

In standard operation, _get_source_fast returns the first template source it finds. It silently ignores TemplateNotFound exceptions from individual loaders until it has exhausted all possibilities.

def _get_source_fast(
self, environment: BaseEnvironment, template: str
) -> tuple[str, str | None, t.Callable[[], bool] | None]:
for _srcobj, loader in self._iter_loaders(template):
try:
return loader.get_source(environment, template)
except TemplateNotFound:
continue
raise TemplateNotFound(template)

Debugging with Template Explanation

When the configuration option EXPLAIN_TEMPLATE_LOADING is set to True, Flask switches to _get_source_explained. This method tracks every attempt made by the loader, recording which loaders were queried and whether they successfully found the template.

This information is then passed to flask.debughelpers.explain_template_loading_attempts, which logs the resolution process. This is particularly useful for diagnosing why a template is being shadowed by another or why a template cannot be found despite existing on the filesystem.

Customizing Template Discovery

While Flask automatically configures the DispatchingJinjaLoader, developers can customize how the global loader is created by overriding the create_global_jinja_loader method in a Flask subclass.

class MyFlask(Flask):
def create_global_jinja_loader(self):
from jinja2 import DictLoader
# Return a custom loader that might include additional sources
return DictLoader({"index.html": "Hello Custom World!"})

However, Flask's internal documentation in src/flask/sansio/app.py notes that overriding this method is generally discouraged. In most cases, customizing the template_folder on the application or individual blueprints is sufficient to achieve the desired template structure.

Constraints and Requirements

For a blueprint or application to participate in template resolution, it must have a template_folder defined. If the template_folder attribute is None (the default for blueprints), the jinja_loader property will return None, and the DispatchingJinjaLoader will skip that component during its search.

Additionally, because blueprints are searched in registration order, if two blueprints provide a template with the same path, the one registered first on the Flask application will always take precedence.