Migrate Trigger conditions to when
For the complete documentation index, see llms.txt. For a full content snapshot, see llms-full.txt. Append.mdto anykestra.io/docs/*URL for plain Markdown.
Available on:Open Source EditionEnterprise Edition
Kestra 2.0 replaces the conditions and preconditions system across all trigger types.
- All trigger types (Schedule, Webhook, HTTP, Flow, and others) — the
conditionslist is removed in favor of a top-levelwhenPebble expression. - Flow triggers — both
conditionsandpreconditionsare removed in favor ofdependsOn(upstream flow entries) andwindow(time window configuration). - Flow trigger outputs — available as
trigger.outputs.<key>(outputs of the last upstream execution). - Input rendering failures — now create a
FAILEDexecution instead of silently dropping the event.
Both conditions and preconditions are removed in Kestra 2.0. Flows that still use them will fail to parse after upgrading.
conditions → when on all triggers
All trigger types gain a top-level when property containing a Pebble expression. When the expression evaluates to true, the trigger fires; when false, it is skipped. This replaces the conditions list, which required a fully qualified Java type for every filtering need and did not compose cleanly across trigger types.
when expression context
The variables available in a when expression depend on the trigger type. For Schedule and Webhook triggers, when is a single top-level expression. Flow triggers have two when locations: the top-level trigger when (evaluated before dependsOn) and when on each dependsOn entry (evaluated against the upstream execution). The table below describes the dependsOn entry context:
| Trigger type | Available variables |
|---|---|
| Schedule | trigger.date |
| Webhook | trigger.body, trigger.headers |
Flow (dependsOn.when) | flow.namespace, flow.id, labels, execution.outputs, execution.state |
flow refers to the upstream flow (the one that just completed). execution.outputs holds the upstream flow’s declared outputs. outputs is also available but holds task outputs, not flow-level outputs — use execution.outputs.<key> to filter on flow outputs.
Schedule date skipping: When a Schedule trigger has a when expression, the scheduler evaluates it against each candidate date. If when evaluates to false, the scheduler skips that date and advances to the next cron-matching date. This is the same behavior as the previous conditions on Schedule triggers; when controls which scheduled dates fire, not just whether a single date fires.
New Pebble helper functions
These functions are introduced specifically for when expressions to replace verbose date formatting patterns:
| Function | Signature | Description |
|---|---|---|
isPublicHoliday | isPublicHoliday(date, countryCode[, subDivision]) | Returns true if the date is a public holiday. Backed by Jollyday. Optional third argument for sub-divisions (e.g. 'IDF'). |
isDayWeekInMonth | isDayWeekInMonth(date, dayOfWeek, position) | Returns true if the date is the Nth occurrence of a weekday in its month. position accepts FIRST, SECOND, THIRD, FOURTH, or LAST. |
isWeekend | isWeekend(date) | Returns true if the date falls on Saturday or Sunday. |
isLastWorkingDay | isLastWorkingDay(date[, workingDays]) | Returns true if the date is the last working day of its month. Working days default to Monday–Friday. Optional second argument overrides which days count as working days. |
dayOfWeek | dayOfWeek(date) | Returns the day name as a string (MONDAY, TUESDAY, …, SUNDAY). |
hourOfDay | hourOfDay(date) | Returns the hour as an integer (0–23). |
dayOfMonth | dayOfMonth(date) | Returns the day of the month as an integer (1–31). |
monthOfYear | monthOfYear(date) | Returns the month as an integer (1–12). |
Existing Pebble filters (startsWith, endsWith, date, timestamp) and operators (and, or, not, ==, !=, >, <, >=, <=) cover the remaining use cases.
Schedule: specific day of week
Before
triggers: - id: schedule type: io.kestra.plugin.core.trigger.Schedule cron: "0 9 * * *" conditions: - type: io.kestra.plugin.core.condition.DayWeek dayOfWeek: MONDAYAfter
triggers: - id: schedule type: io.kestra.plugin.core.trigger.Schedule cron: "0 9 * * *" when: "{{ dayOfWeek(trigger.date) == 'MONDAY' }}"Schedule: weekends only
Before
triggers: - id: schedule type: io.kestra.plugin.core.trigger.Schedule cron: "0 11 * * *" conditions: - type: io.kestra.plugin.core.condition.WeekendAfter
triggers: - id: schedule type: io.kestra.plugin.core.trigger.Schedule cron: "0 11 * * *" when: "{{ isWeekend(trigger.date) }}"Schedule: weekdays only (exclude weekends)
Before
triggers: - id: schedule type: io.kestra.plugin.core.trigger.Schedule cron: "0 9 * * *" conditions: - type: io.kestra.plugin.core.condition.Not conditions: - type: io.kestra.plugin.core.condition.WeekendAfter
triggers: - id: schedule type: io.kestra.plugin.core.trigger.Schedule cron: "0 9 * * *" when: "{{ not isWeekend(trigger.date) }}"Schedule: exclude Sundays
Before
triggers: - id: schedule type: io.kestra.plugin.core.trigger.Schedule cron: "0 9 * * *" conditions: - type: io.kestra.plugin.core.condition.Not conditions: - type: io.kestra.plugin.core.condition.DayWeek dayOfWeek: SUNDAYAfter
triggers: - id: schedule type: io.kestra.plugin.core.trigger.Schedule cron: "0 9 * * *" when: "{{ dayOfWeek(trigger.date) != 'SUNDAY' }}"Schedule: public holidays
Before
triggers: - id: schedule type: io.kestra.plugin.core.trigger.Schedule cron: "0 11 * * *" conditions: - type: io.kestra.plugin.core.condition.PublicHoliday country: FRAfter
triggers: - id: schedule type: io.kestra.plugin.core.trigger.Schedule cron: "0 11 * * *" when: "{{ isPublicHoliday(trigger.date, 'FR') }}"With a sub-division: {{ isPublicHoliday(trigger.date, 'FR', 'IDF') }}.
Schedule: workdays only (not weekend, not public holiday)
Before
triggers: - id: schedule type: io.kestra.plugin.core.trigger.Schedule cron: "0 11 * * *" conditions: - type: io.kestra.plugin.core.condition.Not conditions: - type: io.kestra.plugin.core.condition.PublicHoliday country: FR - type: io.kestra.plugin.core.condition.WeekendAfter
triggers: - id: schedule type: io.kestra.plugin.core.trigger.Schedule cron: "0 11 * * *" when: "{{ not isWeekend(trigger.date) and not isPublicHoliday(trigger.date, 'FR') }}"Schedule: first Monday of the month
Before
triggers: - id: schedule type: io.kestra.plugin.core.trigger.Schedule cron: "0 11 * * 1" conditions: - type: io.kestra.plugin.core.condition.DayWeekInMonth dayOfWeek: MONDAY dayInMonth: FIRSTAfter
triggers: - id: schedule type: io.kestra.plugin.core.trigger.Schedule cron: "0 11 * * 1" when: "{{ isDayWeekInMonth(trigger.date, 'MONDAY', 'FIRST') }}"Schedule: date range
Before
triggers: - id: schedule type: io.kestra.plugin.core.trigger.Schedule cron: "*/5 * * * *" conditions: - type: io.kestra.plugin.core.condition.DateTimeBetween after: "2025-12-31T23:59:59Z" before: "2026-06-30T23:59:59Z"After
triggers: - id: schedule type: io.kestra.plugin.core.trigger.Schedule cron: "*/5 * * * *" when: "{{ (trigger.date | timestamp()) > ('2025-12-31T23:59:59Z' | timestamp()) and (trigger.date | timestamp()) < ('2026-06-30T23:59:59Z' | timestamp()) }}"Inside a when expression trigger.date is a ZonedDateTime, not a string. Comparing it
directly against a string literal raises Could not perform greater than comparison, and the
scheduler emits a FAILED execution on every scheduled date. Convert both sides with
| timestamp(), and always give the literal an explicit offset (Z or ±HH:MM) — a literal
without one is resolved in the server’s default timezone.
Schedule: specific hours only
Before
triggers: - id: schedule type: io.kestra.plugin.core.trigger.Schedule cron: "0 * * * *" conditions: - type: io.kestra.plugin.core.condition.TimeBetween after: "08:00:00" before: "17:00:00"After
triggers: - id: schedule type: io.kestra.plugin.core.trigger.Schedule cron: "0 * * * *" when: "{{ hourOfDay(trigger.date) >= 8 and hourOfDay(trigger.date) < 17 }}"Schedule: combining multiple conditions
Before (first Monday of the month, skip public holidays in France)
triggers: - id: schedule type: io.kestra.plugin.core.trigger.Schedule cron: "0 11 * * *" conditions: - type: io.kestra.plugin.core.condition.DayWeekInMonth dayOfWeek: MONDAY dayInMonth: FIRST - type: io.kestra.plugin.core.condition.Not conditions: - type: io.kestra.plugin.core.condition.PublicHoliday country: FRAfter
triggers: - id: schedule type: io.kestra.plugin.core.trigger.Schedule cron: "0 11 * * *" when: "{{ isDayWeekInMonth(trigger.date, 'MONDAY', 'FIRST') and not isPublicHoliday(trigger.date, 'FR') }}"Webhook: filter by body
Before
triggers: - id: webhook type: io.kestra.plugin.core.trigger.Webhook key: 4wjtkzwVGBM9yKnjm3yv8r conditions: - type: io.kestra.plugin.core.condition.Expression expression: "{{ trigger.body.hello == 'world' }}"After
triggers: - id: webhook type: io.kestra.plugin.core.trigger.Webhook key: 4wjtkzwVGBM9yKnjm3yv8r when: "{{ trigger.body.hello == 'world' }}"Webhook: filter by header and body
Before
triggers: - id: webhook type: io.kestra.plugin.core.trigger.Webhook key: myKey conditions: - type: io.kestra.plugin.core.condition.Expression expression: "{{ trigger.headers['X-Event-Type'] == 'deploy' }}" - type: io.kestra.plugin.core.condition.Expression expression: "{{ trigger.body.environment == 'production' }}"After
triggers: - id: webhook type: io.kestra.plugin.core.trigger.Webhook key: myKey when: "{{ trigger.headers['X-Event-Type'] contains 'deploy' and trigger.body.environment == 'production' }}"trigger.headers maps each header name to a list of values. Use contains rather than == when filtering on a header: trigger.headers['X-Event-Type'] == 'deploy' always evaluates to false because the value is a list. Header names are also matched by the exact casing the sender used; X-Event-Type and x-event-type are different keys.
Multiple Expression conditions combine into a single when expression using and / or.
What replaces what
| Old condition type | New when expression |
|---|---|
DayWeek (e.g. MONDAY) | {{ dayOfWeek(trigger.date) == 'MONDAY' }} |
Weekend | {{ isWeekend(trigger.date) }} |
Not > Weekend (weekdays only) | {{ not isWeekend(trigger.date) }} |
Not > DayWeek SUNDAY (exclude Sundays) | {{ dayOfWeek(trigger.date) != 'SUNDAY' }} |
PublicHoliday (country: FR) | {{ isPublicHoliday(trigger.date, 'FR') }} |
Not > PublicHoliday + Weekend (workdays) | {{ not isWeekend(trigger.date) and not isPublicHoliday(trigger.date, 'FR') }} |
DayWeekInMonth (MONDAY, FIRST) | {{ isDayWeekInMonth(trigger.date, 'MONDAY', 'FIRST') }} |
DateTimeBetween (after/before) | {{ (trigger.date | timestamp()) > ('2025-12-31T23:59:59Z' | timestamp()) and (trigger.date | timestamp()) < ('2026-06-30T23:59:59Z' | timestamp()) }} |
TimeBetween (08:00-17) | {{ hourOfDay(trigger.date) >= 8 and hourOfDay(trigger.date) < 17 }} |
Expression (custom Pebble) | Direct when expression, no wrapper needed |
Expression on webhook body | {{ trigger.body.field == 'value' }} |
Expression on webhook headers | {{ trigger.headers['X-Key'] contains 'value' }} — header values are lists; use contains not ==. The header name must match the exact casing the sender uses. |
Multiple Expression conditions | Combined with and / or in a single when |
For the full list of Pebble calendar helper functions (isWeekend, isPublicHoliday, isDayWeekInMonth, isLastWorkingDay, hourOfDay, etc.), see the date and calendar helpers reference. The timestamp filter used above is documented with the date filters.
conditions and preconditions → dependsOn on Flow triggers
Both conditions (execution-level types such as ExecutionStatus, ExecutionFlow, ExecutionNamespace) and preconditions (upstream flow lists with time windows) are replaced by a single dependsOn list. Each entry declares one upstream dependency with typed properties.
dependsOn entry properties
| Property | Type | Default | Description |
|---|---|---|---|
flowId | string | — | Exact flow ID to match. Omit to match any flow. |
namespace | string | — | Exact namespace to match. Use when for prefix or pattern matching. |
states | list | all terminal states and PAUSED | Execution states that satisfy this entry. |
labels | map | — | Labels the upstream execution must carry (all must match). |
when | string | — | Pebble expression for additional filtering on the upstream execution context. |
Both flowId and namespace use exact matching: namespace: company.team matches only company.team, not company.team.project. For prefix or pattern matching, use when with startsWith or endsWith.
When no states are specified on a dependsOn entry, the trigger evaluates against all terminal states (SUCCESS, WARNING, FAILED, KILLED, CANCELLED, RETRIED, SKIPPED, RESUBMITTED) and PAUSED. Specify states explicitly to narrow the match.
Single upstream flow
The preconditions block and the conditions-based approach both map to a single dependsOn entry.
Before (from preconditions)
triggers: - id: after_extract type: io.kestra.plugin.core.trigger.Flow preconditions: id: flows flows: - namespace: company.team flowId: extract states: [SUCCESS]Before (from conditions)
triggers: - id: on_completion type: io.kestra.plugin.core.trigger.Flow states: [SUCCESS] conditions: - type: io.kestra.plugin.core.condition.ExecutionFlow namespace: company.team flowId: extractAfter
triggers: - id: after_extract type: io.kestra.plugin.core.trigger.Flow dependsOn: - flowId: extract namespace: company.team states: [SUCCESS]Multiple upstream flows with a deadline
Before
triggers: - id: after_staging type: io.kestra.plugin.core.trigger.Flow preconditions: id: staging_deps timeWindow: type: DAILY_TIME_DEADLINE deadline: "09:00:00+01:00" flows: - namespace: company.team flowId: stg_sales states: [SUCCESS] - namespace: company.team flowId: stg_marketing states: [SUCCESS]After
triggers: - id: after_staging type: io.kestra.plugin.core.trigger.Flow dependsOn: - flowId: stg_sales namespace: company.team - flowId: stg_marketing namespace: company.team window: deadline: "09:00:00"states defaults to all terminal states and PAUSED when omitted. window moves to the trigger level. See Window configuration for all window types and the onMiss property.
Multiple upstream flows (from multipleConditions)
Before
triggers: - id: multiple_listen_flow type: io.kestra.plugin.core.trigger.Flow multipleConditions: - id: multiple window: P1D windowAdvance: P0D conditions: flow_a: type: io.kestra.plugin.core.condition.ExecutionFlow namespace: company.team flowId: multiplecondition_flow_a flow_b: type: io.kestra.plugin.core.condition.ExecutionFlow namespace: company.team flowId: multiplecondition_flow_bAfter
triggers: - id: multiple_listen_flow type: io.kestra.plugin.core.trigger.Flow dependsOn: - flowId: multiplecondition_flow_a namespace: company.team states: [SUCCESS] - flowId: multiplecondition_flow_b namespace: company.team states: [SUCCESS] window: every: P1DThe arbitrary string keys (flow_a, flow_b) are dropped; dependsOn is always a list. The windowAdvance property is removed with no direct equivalent.
Namespace-wide alerting (prefix matching)
Before
triggers: - id: alert_on_failure type: io.kestra.plugin.core.trigger.Flow conditions: - type: io.kestra.plugin.core.condition.ExecutionStatus in: - FAILED - WARNING - type: io.kestra.plugin.core.condition.ExecutionNamespace namespace: company comparison: PREFIXAfter
triggers: - id: alert_on_failure type: io.kestra.plugin.core.trigger.Flow dependsOn: - states: [FAILED, WARNING] when: "{{ flow.namespace | startsWith('company') }}"namespace on a dependsOn entry is an exact match. Use flow.namespace in when with startsWith for prefix matching. flow refers to the upstream flow.
Label-based filtering
Before
triggers: - id: after_prod type: io.kestra.plugin.core.trigger.Flow conditions: - type: io.kestra.plugin.core.condition.ExecutionStatus in: [SUCCESS] - type: io.kestra.plugin.core.condition.ExecutionLabels labels: env: productionAfter
triggers: - id: after_prod type: io.kestra.plugin.core.trigger.Flow dependsOn: - namespace: company.team labels: env: production states: [SUCCESS]Conditional filtering with expressions
Before
triggers: - id: after_extract type: io.kestra.plugin.core.trigger.Flow preconditions: id: my_filter where: - id: flow1 filters: - field: NAMESPACE type: STARTS_WITH value: io.kestra.tests - field: EXPRESSION type: IS_TRUE value: "{{ labels.some == 'label' }}"After
triggers: - id: after_extract type: io.kestra.plugin.core.trigger.Flow dependsOn: - when: "{{ flow.namespace | startsWith('io.kestra.tests') }}" states: [SUCCESS] labels: some: labellabels handles exact key-value matching declaratively. when handles everything else.
Filtering on upstream execution outputs
Before
triggers: - id: after_extract type: io.kestra.plugin.core.trigger.Flow conditions: - type: io.kestra.plugin.core.condition.ExecutionOutputs expression: "{{ outputs.row_count > 0 }}"After
triggers: - id: after_extract type: io.kestra.plugin.core.trigger.Flow dependsOn: - flowId: extract namespace: company.team when: "{{ execution.outputs.row_count > 0 }}"In when, execution.outputs.<key> accesses the upstream flow’s declared flow-level outputs. outputs is also available but holds task outputs — {{ outputs.row_count > 0 }} will not resolve a flow output named row_count.
Filtering on retry attempts
Before
triggers: - id: after_flaky type: io.kestra.plugin.core.trigger.Flow conditions: - type: io.kestra.plugin.core.condition.HasRetryAttemptHasRetryAttempt has no working replacement. hasRetryAttempt is not available yet in the when expression context. Until it is, this condition cannot be migrated.
Negation: trigger on any state except SUCCESS
Before
triggers: - id: on_non_success type: io.kestra.plugin.core.trigger.Flow conditions: - type: io.kestra.plugin.core.condition.Not conditions: - type: io.kestra.plugin.core.condition.ExecutionStatus in: [SUCCESS]After
triggers: - id: on_non_success type: io.kestra.plugin.core.trigger.Flow dependsOn: - flowId: extract namespace: company.team states: [FAILED, WARNING, KILLED, CANCELLED]The variable state is not available in the when context. Use an explicit states list on the dependsOn entry to filter by execution state. execution.state is available as a plain string for cases that require an expression — for example, when: "{{ execution.state != 'SUCCESS' }}".
Mixed triggers: success and failure on the same upstream flow
Before
triggers: - id: on_completion type: io.kestra.plugin.core.trigger.Flow states: [SUCCESS] conditions: - type: io.kestra.plugin.core.condition.ExecutionFlow namespace: company.team flowId: flow_a - id: on_failure type: io.kestra.plugin.core.trigger.Flow states: [FAILED] preconditions: id: flowsFailure flows: - namespace: company.team flowId: flow_a states: [FAILED]After
triggers: - id: on_completion type: io.kestra.plugin.core.trigger.Flow dependsOn: - flowId: flow_a namespace: company.team states: [SUCCESS] - id: on_failure type: io.kestra.plugin.core.trigger.Flow dependsOn: - flowId: flow_a namespace: company.team states: [FAILED]Same dependsOn syntax regardless of whether the original used conditions or preconditions.
Passing outputs downstream
When a Flow trigger fires, trigger.outputs.<key> gives access to the upstream execution’s outputs.
Before
triggers: - id: after_extract type: io.kestra.plugin.core.trigger.Flow inputs: date: "{{ trigger.outputs.date }}" preconditions: id: flows flows: - namespace: company.team flowId: extract states: [SUCCESS]After
triggers: - id: after_extract type: io.kestra.plugin.core.trigger.Flow inputs: date: "{{ trigger.outputs.date }}" dependsOn: - flowId: extract namespace: company.teamtrigger.outputs.<key> holds the outputs of the last upstream execution. For single-flow triggers, this is always the correct execution. For multi-flow triggers (mode: ALL or mode: ANY with multiple dependsOn entries), only the last-completed upstream flow’s outputs are available through this path; per-flow output access is not yet supported.
ForEachItem chain
When using Flow triggers to chain ForEachItem child flows, reference the child flow’s outputs using trigger.outputs.<key>:
Before
triggers: - id: 01_complete type: io.kestra.plugin.core.trigger.Flow inputs: testFile: "{{ trigger.outputs.myFile }}" preconditions: id: output_01_success flows: - namespace: io.kestra.tests.trigger.foreachitem flowId: flow-trigger-for-each-item-child states: [SUCCESS]After
triggers: - id: 01_complete type: io.kestra.plugin.core.trigger.Flow inputs: testFile: "{{ trigger.outputs.myFile }}" dependsOn: - flowId: flow-trigger-for-each-item-child namespace: io.kestra.tests.trigger.foreachitemmode: OR and N-of-M logic
The mode property controls how dependsOn entries are combined when evaluating whether to fire.
| Value | Behavior | Required properties |
|---|---|---|
ALL (default) | Fires when all dependsOn entries are satisfied | — |
ANY | Fires as soon as any one entry is satisfied | — |
AT_LEAST | Fires when at least minSatisfied entries are satisfied | minSatisfied (integer ≥ 1, ≤ entry count) |
OR logic: fire when any upstream completes
Previously, OR logic required N separate Flow triggers. mode: ANY consolidates them into one.
Before (two separate triggers)
triggers: - id: on_salesforce type: io.kestra.plugin.core.trigger.Flow conditions: - type: io.kestra.plugin.core.condition.ExecutionFlow namespace: company.sources flowId: ingest_salesforce - type: io.kestra.plugin.core.condition.ExecutionStatus in: [SUCCESS] - id: on_hubspot type: io.kestra.plugin.core.trigger.Flow conditions: - type: io.kestra.plugin.core.condition.ExecutionFlow namespace: company.sources flowId: ingest_hubspot - type: io.kestra.plugin.core.condition.ExecutionStatus in: [SUCCESS]After
triggers: - id: react_to_any_source type: io.kestra.plugin.core.trigger.Flow mode: ANY dependsOn: - flowId: ingest_salesforce namespace: company.sources states: [SUCCESS] - flowId: ingest_hubspot namespace: company.sources states: [SUCCESS]mode: ANY fires as soon as either dependency is satisfied. The default mode: ALL requires every entry to be satisfied before the trigger fires.
OR logic with a time window
triggers: - id: daily_any_source type: io.kestra.plugin.core.trigger.Flow mode: ANY dependsOn: - flowId: ingest_salesforce namespace: company.sources - flowId: ingest_hubspot namespace: company.sources window: deadline: "09:00:00"Fire before 9 AM when either source completes.
N of M: at least 2 out of 3
triggers: - id: partial_success type: io.kestra.plugin.core.trigger.Flow mode: AT_LEAST minSatisfied: 2 dependsOn: - flowId: ingest_salesforce namespace: company.sources states: [SUCCESS] - flowId: ingest_hubspot namespace: company.sources states: [SUCCESS] - flowId: ingest_zendesk namespace: company.sources states: [SUCCESS] window: deadline: "09:00:00"mode: AT_LEAST fires when minSatisfied entries are satisfied. minSatisfied must be ≥ 1 and ≤ the number of dependsOn entries.
What replaces what
| Old property / condition type | New equivalent |
|---|---|
conditions list on Flow trigger | dependsOn list |
preconditions block | dependsOn list + window |
multipleConditions block | dependsOn list + window.every |
ExecutionStatus (in: [SUCCESS]) | states: [SUCCESS] on the dependsOn entry |
ExecutionFlow (flowId, namespace) | flowId + namespace on the dependsOn entry |
ExecutionNamespace (exact) | namespace on the dependsOn entry |
ExecutionNamespace (comparison: PREFIX) | when: "{{ flow.namespace | startsWith('...') }}" on the entry |
ExecutionLabels (labels: {k: v}) | labels: {k: v} on the dependsOn entry |
ExecutionOutputs (expression) | when with execution.outputs.<key> on the entry (flow-level outputs) |
HasRetryAttempt | no working replacement — hasRetryAttempt is not available yet in the when context |
Not > ExecutionStatus | Explicit states list only — state is not available in the when context; use execution.state for expression-based checks |
where filter REGEX | {{ flow.id | regexMatch('^pattern$') }} — note: regexMatch matches anywhere in the value (partial match), while 1.3 REGEX matched the whole value. Use ^...$ anchors to preserve 1.3 behavior. |
| Multiple triggers for OR logic | mode: ANY with dependsOn entries |
preconditions.resetOnSuccess: true | remove it, this is the only behavior in 2.0 |
timeWindow.type: DAILY_TIME_DEADLINE | window.deadline |
timeWindow.type: DAILY_TIME_WINDOW | window.from + window.to |
timeWindow.type: DURATION_WINDOW | window.every |
timeWindow.type: SLIDING_WINDOW | window.lookback |
Window configuration
The window property applies to Flow triggers and controls how Kestra accumulates upstream executions before evaluating dependsOn entries. Set exactly one property group per window; combining groups is a validation error.
| Window type | Properties | Behavior |
|---|---|---|
| Deadline | deadline: "09:00:00" | Upstream flows must complete by a fixed time each day |
| Daily time range | from: "06:00:00" + to: "12:00:00" | Only executions within a daily time range count |
| Fixed interval | every: P1D + optional offset: PT6H | Recurring window of a fixed size, offset from midnight |
| Lookback | lookback: PT1H | Rolling window looking back from the current evaluation time |
None of the window types changes how often the trigger fires. Once every dependsOn entry has been satisfied and an execution has been created, the stored results are reset, so every dependency has to be satisfied again before another execution is created.
Deadline
window: deadline: "09:00:00"Daily time range
window: from: "06:00:00" to: "12:00:00"Fixed interval
window: every: P1D offset: PT6HLookback
window: lookback: PT1HReplacing timeWindow types
Old timeWindow.type | New window property |
|---|---|
DAILY_TIME_DEADLINE | deadline: "09:00:00" |
DAILY_TIME_WINDOW | from: "06:00:00" + to: "12:00:00" |
DURATION_WINDOW | every: P1D + optional offset: PT6H |
SLIDING_WINDOW | lookback: PT1H |
preconditions.resetOnSuccess: true can be removed, since resetting after firing is the only behavior in 2.0. resetOnSuccess: false has no equivalent; use mode: ANY if you want an execution to be created as soon as any single upstream flow succeeds.
Behavior changes after upgrading
Silent failures → FAILED executions
Previously, if an expression on a Flow trigger failed to render (for example, because an upstream output key did not exist), the trigger silently dropped the event and no execution was created. In Kestra 2.0, a FAILED execution is created instead, making failures visible in the UI and actionable via downstream alerting.
No migration action is required. Review your Flow trigger inputs expressions to ensure they reference valid output keys and avoid unexpected FAILED executions after upgrading.
State store reset and in-flight events
Previously, auto-generated condition keys (condition_1, condition_2, …) meant that reordering entries could reset accumulated window state. In Kestra 2.0, dependsOn entry keys are derived from each entry’s namespace and flowId, making them order-independent.
The trigger-level state store key also changes: the old scheme used preconditions.id; the new scheme uses {flowId}/{triggerId}. Existing accumulated state from preconditions will not be found after upgrading; in-flight multi-flow triggers re-evaluate from scratch. For most deployments this means at most one missed trigger cycle.
Old-format events in the async queue are discarded gracefully (logged as a warning). No user action is required.
Migration steps
- Replace
conditions:on all triggers with awhen:Pebble expression. This applies to Schedule, Webhook, HTTP, and any other trigger type that usedconditions. - Replace
conditions:andpreconditions:on Flow triggers withdependsOn:entries and (if applicable)window:. - Check
dependsOnstatesvalues. When omitted,statesdefaults to all terminal states andPAUSED. Addstatesexplicitly on any entry that should match only specific states. - Check
trigger.outputsreferences. The formtrigger.outputs.<key>holds the outputs of the last upstream execution. For single-flow triggers, no change is needed. For multi-flow triggers, only the last-completed upstream flow’s outputs are accessible; per-flow output access is not yet available. - Update
timeWindowtowindowusing the property mapping table above. - Validate by saving updated flows in the Kestra UI or via the API and confirming they parse without errors.
Was this page helpful?