Pass data and handle errors
A key feature of workflows is the ability to pass data between steps and handle failures gracefully. This page explains the mechanisms for controlling data flow and building resilient, fault-tolerant automations.
Every step in a workflow produces an output. By default, this output is added to a global steps object in the workflow's context, making it available to all subsequent steps.
Use the following syntax to access the output of a specific step:
steps.<step_name>.output
You can also access error information from a step:
steps.<step_name>.error
This example demonstrates a common pattern: searching for data in one step and using the results in a later step. In this case, the workflow searches for a specific user's full name, then uses it to create a new security case.
name: Create case for a specific user
steps:
- name: find_user_by_id
type: elasticsearch.search
with:
index: "my-user-index"
query:
term:
user.id: "u-123"
- name: create_case_for_user
type: cases.createCase
with:
title: "Investigate user u-123"
description: "A case has been opened for user {{ steps.find_user_by_id.output.hits.hits[0]._source.user.fullName }}."
owner: "securitySolution"
tags: ["user-investigation"]
In this example:
- The
find_user_by_idstep searches an index for a document. - The
create_case_for_userstep uses the output of the first step to enrich a new Elastic Security case. - The
descriptionfield accessessteps.find_user_by_id.output.hits.hits[0]._source.user.fullNameto dynamically include the user's full name in the case description.
By default, if any step fails the entire workflow execution stops immediately (the abort behavior). Override this with the on-failure block, which supports retry logic, fallback steps, and continuation. For failures that cross workflow boundaries, the workflows.failed trigger lets a separate handler workflow react after another workflow has failed.
| Layer | What it controls | Use for |
|---|---|---|
Per-step on-failure |
What happens when one step fails. | Retry transient failures, continue past non-critical steps, or provide a fallback. |
Workflow-level settings.on-failure |
Default on-failure applied to every step. |
A consistent global retry policy. |
Cross-workflow workflows.failed trigger |
A separate handler workflow that runs after another workflow has failed. | Paging on-call, opening cases, central error reporting. |
You can configure on-failure at two levels:
Step-level — applies to a specific step:
steps:
- name: api-call
type: http
on-failure:
retry:
max-attempts: 3
delay: "5s"
Workflow-level (configured under settings) — applies to all steps as the default error handling behavior:
settings:
on-failure:
retry:
max-attempts: 2
delay: "1s"
steps:
- name: api-call
type: http
Precedence: per-step on-failure > workflow-level settings.on-failure > engine default (abort).
Step-level on-failure configuration always overrides workflow-level settings.
Retries the failed step a configurable number of times. The full shape accepts backoff strategy, maximum delay, jitter, and a KQL condition so you only retry on specific errors.
on-failure:
retry:
max-attempts: 5
delay: "1s" # Base delay between attempts. Duration format, for example "5s", "1m".
strategy: exponential # "fixed" (default) or "exponential".
multiplier: 2
max-delay: "30s"
jitter: true
condition: "steps.self.error.status : 429"
- Total attempts, including the first. Required, minimum 1.
- Only used with strategy: exponential.
- Ceiling on the delay between retries.
- Add randomness to avoid thundering-herd retry storms.
- Optional KQL predicate over steps.self.error.
condition is a KQL expression evaluated against steps.self.error. Use it to retry only on specific failure modes: for example, retry on HTTP 429s and 5xxs but not on 4xx client errors.
The workflow fails when all retries are exhausted, unless paired with fallback or continue.
Runs alternative steps after the primary step fails and all retries are exhausted. In the following example, when the delete_critical_document step fails, the workflow runs two additional steps: one sends a Slack notification to devops-alerts using {{workflow.name}}, while the other logs the error details from the failed step using {{steps.delete_critical_document.error}}.
on-failure:
fallback:
- name: notify_on_failure
type: slack
connector-id: "devops-alerts"
with:
message: "Failed to delete document in workflow '{{workflow.name}}'"
- name: log_failure
type: console
with:
message: "Document deletion failed, error: {{steps.delete_critical_document.error}}"
Within fallback steps, access error information from the failed primary step using steps.<failed_step_name>.error.
Continues workflow execution even if a step fails. The failure is recorded at steps.<name>.error, but the workflow moves on to the next step. Use this for non-critical steps whose failure shouldn't take down the whole workflow.
on-failure:
continue: true
Stops the workflow. This is the default when no on-failure is configured, so you rarely need to write it explicitly. Use abort when a downstream step depends on this step's output and continuing makes no sense.
You can combine multiple failure-handling options. They are processed in this order: retry → fallback → continue.
In the following example:
- The step retries up to 2 times with a 1-second delay.
- If all retries fail, the fallback steps run.
- The workflow continues regardless of the outcome.
- name: create_ticket
type: jira
connector-id: "my-jira-project"
with:
projectKey: "PROJ"
summary: "New issue from workflow"
on-failure:
retry:
max-attempts: 2
delay: "1s"
fallback:
- name: notify_jira_failure
type: slack
connector-id: "devops-alerts"
with:
message: "Warning: Failed to create ticket. Continuing workflow."
continue: true
- Flow-control steps (
if,foreach) cannot have workflow-levelon-failureconfigurations. - Fallback steps run only after all retries have been exhausted.
- When combined, failure-handling options are processed in this order: retry → fallback → continue.
For production-critical workflows, the final layer is a separate handler workflow that fires when another workflow fails. The workflows.failed trigger fires after a workflow execution reaches the failed terminal state, so you can build handlers that page on-call, open a case, or post to a dedicated index for workflow-failure observability. Refer to Event-driven triggers for the trigger reference and examples.
| Problem | Use |
|---|---|
| "This API is flaky and should retry automatically." | Per-step on-failure: retry. |
| "Every step in this workflow should get 2 retries by default." | Workflow-level settings.on-failure: retry. |
| "This step is nice-to-have, so don't fail the workflow if it dies." | Per-step on-failure: continue. |
| "Try the primary API, and if it fails, use the backup API." | Per-step on-failure: fallback. |
| "When a production workflow fails, page on-call and open a case." | A separate workflows.failed handler workflow. |
| "This workflow is critical and I want monitoring on its failure rate." | workflows.failed handler that writes to an index, plus your existing observability stack. |
Workflows support dynamic values through template variables and template expressions.
- Template variables: The data you reference, such as step outputs (
steps.<name>.output), constants (consts.<name>), inputs (inputs.<name>), and context variables (execution.id,event). - Template expressions: The syntax used to insert variables. Use
{{ }}for string output or${{ }}to preserve data types like arrays and objects.
Template variables are the data sources you can reference inside template expressions. The following template variables are available:
| Variable type | Syntax | Description |
|---|---|---|
| Step outputs | steps.<step_name>.output |
Data produced by each step during execution. Access results from previous steps to chain operations together. Refer to Reference outputs for more details. |
| Constants | consts.<constant_name> |
Reusable values defined once at the workflow level using the consts block. Refer to Reference constants for more details. |
| Inputs | inputs.<input_name> |
Parameters defined in the inputs block that can be provided when the workflow is triggered. Refer to Reference inputs for more details. |
| Context variables | execution.id, event, foreach.item |
Data automatically provided by the workflow engine at runtime, including execution metadata, trigger data, and loop state. Refer to Context variables reference for more details. |
Constants and inputs are both template variables that let you define reusable values in your workflow, but they serve different purposes:
- Constants — Use for values that are fixed for the workflow definition and don't change between runs, such as index names, API endpoints, and threshold values.
- Inputs — Use for values that might vary each time the workflow runs, such as user-provided parameters, environment toggles, or any value that changes per execution.
Use template expressions to insert template variables into your workflow. The templating engine supports two syntax options:
| Syntax | Purpose | Example |
|---|---|---|
{{ }} |
Insert values as strings | "Hello, {{user.name}}" |
${{ }} |
Preserve data types (arrays, objects, numbers) | ${{steps.search.output.hits}} |
For syntax details and examples, refer to Templating engine.
By combining data flow, templating, and robust error handling, you can build complex, reliable automations that react to dynamic conditions and recover from unexpected failures.
| Action | Syntax | Description |
|---|---|---|
| Step output | steps.<step_name>.output |
Access the result of a previous step. |
| Step error | steps.<step_name>.error |
Access error details from a failed step. |
| Constants | consts.<constant_name> |
Access workflow-level constants. |
| Inputs | inputs.<input_name> |
Access parameters passed at trigger time. |
| Execution metadata | execution.id, execution.startedAt |
Access information about the current run. |
| Trigger data | event |
Access data from the trigger that started the workflow. |
| Retry on failure | on-failure.retry |
Retry a failed step with optional delay. |
| Fallback steps | on-failure.fallback |
Define recovery actions when a step fails. |
| Continue on failure | on-failure.continue: true |
Allow the workflow to proceed after a failure. |