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:
- The Application Loader: Flask first checks the
template_folderconfigured on theFlaskapplication instance. - 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.