Use Jinja templates in Airflow

Overview

Airflow supports templating, which enables dynamic content generation at runtime using variables, macros, and control structures. This feature enables DAGs and tasks to adapt based on execution context such as dates, task instances, and custom logic.

The templating framework used in Airflow is Jinja. It’s a Python templating engine that allows you to embed dynamic expressions and control structures inside text files.

This article focuses on how to use Jinja templates within DAGs and operators in Airflow, diving into core usage patterns, templated fields, macros, and examples.

Jinja in Airflow

Airflow renders specific fields called templated fields using Jinja at runtime. These fields are defined on the operator level. Common operators such as BashOperator, PythonOperator, EmailOperator, and SqlOperator have templated fields like bash_command, sql, and subject.

For all available options, see the Airflow template reference.

An example of using Jinja templates:

from airflow.operators.bash import BashOperator
from airflow import DAG
from datetime import datetime (1)

with DAG("templating_example", start_date=datetime(2023, 1, 1), schedule_interval="@daily", catchup=False) as dag: (2)
    t1 = BashOperator(
        task_id='print_date',
        bash_command='echo "Execution date is {{ ds }}"', (3)
    )
1 Import the dependencies.
2 Initialize a DAG.
3 Add a template in the output string. In this example, {{ ds }} is replaced with the execution date at runtime (e.g. 2025-04-22).

Template usage restrictions

To inject dynamic content into operator parameters, use the {{ …​ }} and {% …​ %} syntax.

Templating is allowed in:

  • Variables. For example: {{ ds }}, {{ task_instance }}, and others.

  • Control structures. For example: {% if …​ %}, {% for …​ %}.

  • Custom expressions. For example: {{ macros.ds_add(ds, 7) }}.

Check the operator documentation for which fields are templated. You can also define custom templated fields by extending an operator.

Template search path

Airflow can load external template files from a directory. This allows you to separate your SQL queries and report templates into different files.

For example:

dag = DAG(
    dag_id='template_path_example',
    start_date=datetime(2023, 1, 1),
    schedule_interval='@daily',
    template_searchpath=['/opt/airflow/templates/sql']
)

where template_searchpath — path to SQL templates.

PythonOperators

Jinja can be combined with Python functions by rendering templates before task execution. You can pass templated strings via op_kwargs.

For example:

from airflow.operators.python import PythonOperator

def greet_user(execution_date, **kwargs):
    print(f"Hello! Today is {execution_date}")

PythonOperator(
    task_id='greet',
    python_callable=greet_user,
    op_kwargs={"execution_date": "{{ ds }}"},
    dag=dag,
)

Airflow will render "{{ ds }}" into the actual date before calling greet_user.

Macros and filters

Airflow includes built-in macros like macros.ds_add, macros.datetime, filters like | ds_format, | replace, and others.

For example:

BashOperator(
    task_id='seven_days_later',
    bash_command='echo "In 7 days: {{ macros.ds_add(ds, 7) }}"',
)

Custom macros can also be defined in your DAG or loaded via plugins.

Create a DAG using Jinja templates

Below is an example of how to build a DAG that generates a dynamic daily report in Markdown using Jinja templates.

Step 1. Prepare the template file

Create a Jinja template file that will be used to generate reports.

  1. Create a folder for templates inside your Airflow environment:

    $ sudo mkdir -p /opt/airflow/templates
  2. In the templates directory, create a file called report_template.md with the following contents:

    # Daily report
    
    **Date:** {{ ds }}
    **Task Status:** {{ task_instance.state }}
    
    ## Summary
    - DAG ID: {{ dag.dag_id }}
    - Task ID: {{ task.task_id }}

    The values in curly brackets are placeholders, which will be filled dynamically at runtime.

Step 2. Write the Python code for the DAG

Create a new Python file dynamic_report_dag.py in your DAGs folder, and add this code:

from airflow import DAG (1)
from airflow.operators.python import PythonOperator
from datetime import datetime
from jinja2 import Environment, FileSystemLoader

def render_report(ds, **context): (2)
    env = Environment(loader=FileSystemLoader('/opt/airflow/templates'))
    template = env.get_template('report_template.md')
    report = template.render(
        ds=ds,
        dag=context['dag'],
        task=context['task'],
        task_instance=context['ti']
    )

    output_path = f"/opt/airflow/reports/report_{ds}.md" (3)
    with open(output_path, "w") as f:
        f.write(report)

with DAG(  (4)
    dag_id="dynamic_report_dag",
    start_date=datetime(2023, 1, 1),
    schedule_interval="@daily",
    catchup=False,
    template_searchpath=['/opt/airflow/templates'],
) as dag:

    generate_report = PythonOperator(
        task_id='generate_report',
        python_callable=render_report,
        provide_context=True,
    )
1 Import the dependencies.
2 Define the function to render the Jinja template.
3 Save the rendered report.
4 Define the DAG. For more information on how to create a DAG, see the Create a simple DAG article.

Step 3. Create the reports directory

  1. Create a directory where the generated reports will be saved:

    $ sudo mkdir -p /opt/airflow/reports
  2. Set the necessary access settings:

    $ sudo chmod -R 755 /opt/airflow/reports/
    $ sudo chown -R airflow:airflow /opt/airflow/reports

Step 4. Trigger the DAG

  1. Go to the Airflow web UI.

  2. Find the dynamic_report_dag DAG and unpause it.

  3. Trigger the DAG.

Once the DAG finishes running, you will see the file in the reports directory with the current date in it’s name and the similar content:

# Daily report

**Date:** 2025-04-28
**Task Status:** running

## Summary
- DAG ID: dynamic_report_dag
- Task ID: generate_report

The file contains the execution date, task status, DAG ID, and task ID based on the run context.

Found a mistake? Seleсt text and press Ctrl+Enter to report it