pallets/flask · error · TemplateNotFound

{template}

Error message

{template}

What it means

A `jinja2.TemplateNotFound` raised by Flask's DispatchingJinjaLoader in `_get_source_explained` (used when `EXPLAIN_TEMPLATE_LOADING=True`). Flask tried every loader — the app's `templates/` folder and each registered blueprint's template folder — and none could supply the named template. In this explain path, Flask first logs a detailed report of every loader attempted before raising.

Source

Thrown at src/flask/templating.py:86

        rv: tuple[str, str | None, t.Callable[[], bool] | None] | None
        trv: None | (tuple[str, str | None, t.Callable[[], bool] | None]) = None

        for srcobj, loader in self._iter_loaders(template):
            try:
                rv = loader.get_source(environment, template)
                if trv is None:
                    trv = rv
            except TemplateNotFound:
                rv = None
            attempts.append((loader, srcobj, rv))

        from .debughelpers import explain_template_loading_attempts

        explain_template_loading_attempts(self.app, template, attempts)

        if trv is not None:
            return trv
        raise TemplateNotFound(template)

    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)

    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

View on GitHub ↗ (pinned to 6a2f545bfd)

Solutions

  1. Check the explain-template-loading log output just emitted — it lists every loader and path Flask searched; put the file where one of those paths points.
  2. Fix the filename/case in `render_template()` to exactly match the file on disk.
  3. Pass `template_folder='templates'` when constructing the Blueprint if the template lives with the blueprint.
  4. For installed packages, ensure templates are included in the distribution (package_data or MANIFEST.in).

Example fix

# before
bp = Blueprint("admin", __name__)
render_template("admin/Index.html")

# after
bp = Blueprint("admin", __name__, template_folder="templates")
render_template("admin/index.html")  # matches on-disk case
Defensive patterns

Strategy: try-catch

Validate before calling

import os
tpl = 'user/profile.html'
exists = os.path.exists(os.path.join(app.root_path, app.template_folder, tpl))

Type guard

def template_exists(app, name):
    return app.jinja_env.get_or_select_template is not None and name in app.jinja_env.list_templates()

Try / catch

from jinja2 import TemplateNotFound
try:
    return render_template('page.html')
except TemplateNotFound:
    abort(404)

Prevention

When it happens

Trigger: `render_template('name.html')` (or `render_template_string` extending a missing template, `{% include %}`/`{% extends %}` of a missing file) when the file doesn't exist in the app's template_folder or any blueprint's template_folder, with EXPLAIN_TEMPLATE_LOADING enabled.

Common situations: Typo or wrong case in the template filename (case-sensitive on Linux, not on macOS/Windows dev machines); templates placed next to the module instead of in `templates/`; blueprint created without `template_folder=` set; package installed without including template files (missing package_data/MANIFEST.in); wrong `template_folder` path relative to the app root.

Related errors


AI-assisted analysis of pallets/flask@6a2f545bfd (2026-07-31). Data as JSON: /data/errors/92f2922247de286a.json. Report an issue: GitHub ↗.