TriggerDagRunOperator starts a run of another DAG and sends it a dictionary of values, the conf. The triggered DAG declares the values it accepts as params, with defaults and types, so the same DAG can also run on its own or from the UI form.

Copy and paste

The DAG that triggers:

from airflow.providers.standard.operators.trigger_dagrun import TriggerDagRunOperator

trigger_export = TriggerDagRunOperator(
    task_id="trigger_export",
    trigger_dag_id="export_sales",
    conf={"region": "us", "full_refresh": True, "run_date": "{{ ds }}"},
)

The DAG that is triggered:

import pendulum
from airflow.sdk import dag, task, Param

@dag(
    schedule=None,
    start_date=pendulum.datetime(2026, 1, 1, tz="UTC"),
    params={
        "region": Param("eu", type="string", enum=["eu", "us", "apac"]),
        "full_refresh": Param(False, type="boolean"),
        "run_date": Param(None, type=["null", "string"]),
    },
)
def export_sales():
    @task
    def export(params=None):
        print(params["region"], params["full_refresh"], params["run_date"])

    export()

export_sales()

In a templated field the same values are {{ params.region }}, {{ params.run_date }} and so on.

Wait until the triggered run has finished:

trigger_export = TriggerDagRunOperator(
    task_id="trigger_export",
    trigger_dag_id="export_sales",
    conf={"region": "us"},
    wait_for_completion=True,
    poke_interval=30,
    deferrable=True,   # frees the worker slot while waiting; needs the triggerer
)

How the values arrive

conf is a templated field, so "{{ ds }}" becomes the date of the triggering run. The triggered DAG receives the dictionary as dag_run.conf, and every key it sends replaces the default of the matching param. Keys you do not send keep their default: with the conf above, full_refresh would be False if it were left out.

Params also validate the input. A value outside the enum, such as "region": "mars", fails the validation and the run does not start.

The logical date of the triggered run

Unless you pass logical_date, the triggered run gets the moment of the trigger as its logical date, not the date of the DAG that triggered it. Its ds is therefore today. That is why the example sends the business date as run_date in the conf.

You can pass logical_date="{{ logical_date }}" instead, but a DAG accepts one run per logical date. Triggering the same date a second time, for example when the calling task is retried, fails with a message that the run already exists. Two parameters handle that case:

Other details

Tested with Airflow 3.3.2 and the standard provider 1.19.

References