template not found" Jinja error in Airflow tasks
Fixes Airflow tasks that fail with Jinja's template not found error. Use when a task cant find a .sql or template file, when templates render locally but fail in the deployed environment, or when template_searchpath needs checking. Not for Jinja syntax errors inside a template that was found, for missing DAG files, or for connection errors.
TL;DR
Jinja cant find your template file because the directory it lives in isnt on Airflow's template search path. The fix is to set template_searchpath on the DAG to the folder holding your templates, anchored to the DAG file's own directory so it works in every environment. This happens because Jinja only looks in its configured search paths, and the DAG file's folder is not automatically one of them.
jinja2.exceptions.TemplateNotFound: my_query.sqlUse this when
- A task fails at render time with TemplateNotFound naming a file you know exists
- The template works on your laptop but not on the scheduler or workers
- You recently moved templates into a subfolder like templates/
Not for this skill when
- Jinja finds the file but chokes on its syntax (thats a template bug, not a path bug)
- The DAG file itself is missing or failing to import
- The error is a connection or permission problem, not a missing template
Steps
- Reproduce the failure with the exact task and logical date:
airflow tasks test my_dag my_task 2026-10-01Expected output: the task fails during rendering with jinja2.exceptions.TemplateNotFound naming your file, confirming this is a path problem and not a query problem.
- Inspect which search paths the task is actually using:
from airflow.models import DagBag
bag = DagBag(dag_folder="[dag folder]", include_examples=False)
task = bag.get_dag("my_dag").get_task("my_task")
print(task.template_searchpath)Expected output: a list of directories Jinja searches. Your templates folder is probably not in it, which is the whole bug.
- Anchor the search path to the DAG file's directory:
from pathlib import Path
TEMPLATE_DIR = str(Path(__file__).resolve().parent / "templates")
with DAG(
dag_id="my_dag",
template_searchpath=[TEMPLATE_DIR],
) as dag:
...Expected output: template_searchpath now points at the real templates folder in every environment, because it is derived from the DAG file location instead of a hardcoded path.
- Reference templates by filename (or subfolder-relative path) in your operators:
MyOperator(
task_id="my_task",
sql="my_query.sql",
)Expected output: the operator finds templates/my_query.sql at render time. If the file sits in a subfolder, use a relative path like staging/my_query.sql.
- Re-run the task test from step 1.
airflow tasks test my_dag my_task 2026-10-01Expected output: the task renders the template and proceeds to execute, with no TemplateNotFound in the logs.
- If you deploy with containers or a remote executor, verify the templates exist where the worker runs, not just on your laptop:
ls [templates dir]/my_query.sqlExpected output: the listing shows your template file. A correct path pointing at files that were never shipped is the same error with a different fix: ship the files.
Variant phrasings
jinja2.exceptions.TemplateNotFound
The full exception path you see in task logs. Everything above applies; the class name just tells you Jinja raised it rather than Airflow itself.
TemplateNotFound for a template in a subfolder
Reference it relative to the search path root, like staging/my_query.sql, and make sure the search path points at the folder containing staging/, not at staging/ itself. Pointing one level too deep is a common variant of this bug.
template not found only in production
Classic environment skew: the search path was built from a local absolute path, or the templates folder is not mounted in the deployed environment. Anchoring to the DAG file (step 3) plus verifying the files ship (step 6) covers both causes.
Why it happens
Jinja's FileSystemLoader resolves template names against a fixed list of search directories, and Airflow builds that list from the DAG's template_searchpath. The DAG file's own directory is not added automatically, so a template sitting next to the DAG is invisible unless you say so. Local absolute paths make it worse: they work on your machine and nowhere else, which is why this error loves to appear only in production.
Edge cases
- Case sensitivity:
My_Query.sqlandmy_query.sqlare different files on Linux. Match the filename exactly, including case. - Templates referenced with an absolute path bypass the search path entirely; they work until the environment changes, then break the same way.
- Multiple DAGs sharing one templates folder: have each DAG compute the folder from
__file__so moving the DAG folder doesnt silently break the path. - Cached DAG parsing: after fixing the path, a stale parsed DAG can linger for a cycle. Re-run the import-error check or wait one parse interval before concluding the fix failed.
Provenance
Resolved from the public thread: https://vectle.com/posts/pstqzKMoB4LaWjEohPimEpHA
Maintainer review
No maintainer verification is recorded for this version.
This records the version a maintainer checked. It does not assert that the version is the latest upstream release.