Workflow Helper Functions
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.
This group is more situational, but it becomes valuable in complex flows where you need to inspect sibling results, build links back into Kestra, or summarize failures.
loopOutputs()
Extracts a named output from every iteration of a Loop task and returns the values as a list.
Prerequisite: the Loop task must declare an outputs: block. Without it, outputs.loop.outputs does not exist and this function will fail.
{{ loopOutputs(outputs.myLoop.outputs, 'result') }}The first argument must be the loop task’s .outputs list. The second argument is the output ID, which must match an output ID declared in the Loop task’s outputs: block. The function returns one value per iteration, with null for iterations where that key is missing.
The values follow the order in which iterations finished, not the order of values. With concurrencyLimit: 1, iterations run one at a time, so the two orders match. When iterations run in parallel, which is the Loop default, the order can change from one execution to the next. Sort the list when you need a stable result.
For a Loop over ["a", "b", "c"] with a declared output result:
{{ loopOutputs(outputs.loop.outputs, 'result') }}{# → ["processed c", "processed a", "processed b"], in completion order #}
{{ loopOutputs(outputs.loop.outputs, 'result') | sort }}{# → ["processed a", "processed b", "processed c"] #}Each entry in outputs.loop.outputs holds one iteration: item.iteration is its zero-based index, item.value its input value, and outputs its declared outputs. Because the list is in completion order, outputs.loop.outputs[0] is the first iteration to finish. To read a specific iteration, filter on item.iteration:
{{ outputs.loop.outputs | jq('.[] | select(.item.iteration == 0) | .outputs.result') | first }}errorLogs()
Prints all error logs from the current execution:
{{ errorLogs() }}It is most useful in errors blocks, where you need a compact summary of what failed without manually traversing task state objects.
tasksWithState()
Returns a list of task run objects matching the given state. Use it in error handlers or notifications to report which tasks failed:
{{ tasksWithState('FAILED') }}Useful for building conditional logic or failure summaries based on task outcomes.
subflow()
Synchronously runs a subflow and returns its terminal execution result, so you can read the subflow’s outputs, state, or labels from within an expression.
{{ subflow(namespace='company.team', id='my_subflow', inputs={'key': 'value'}).outputs.my_output }}Arguments:
| Argument | Required | Description |
|---|---|---|
namespace | Yes | Namespace of the subflow to run |
id | Yes | Flow ID of the subflow to run |
inputs | No | Map of inputs to pass to the subflow |
revision | No | Specific revision to run; defaults to latest |
labels | No | Labels to attach to the subflow execution |
timeout | No | ISO 8601 duration; defaults to PT1M, hard cap PT5M |
Return value — an object with four fields:
| Field | Type | Description |
|---|---|---|
.id | string | Execution ID of the subflow run |
.state | string | Terminal state name, e.g. SUCCESS, FAILED |
.outputs.<key> | any | Flow-level outputs declared in the subflow’s outputs: block |
.labels.<key> | string | Execution labels as a key → value map |
Primary use case — populating a SELECT or MULTISELECT input’s expression: at form render time:
inputs: - id: datacenter type: SELECT expression: "{{ subflow(namespace='company.ops', id='fetch_datacenters').outputs.datacenter_list }}"When a user opens the Execute form, Kestra runs the subflow synchronously, reads its output, and populates the dropdown before the main flow begins.
Important constraints:
- Only valid in an input’s
expression:ordefaults:. Usingsubflow()inside a task or trigger property throws an error, because blocking a worker thread while waiting for a child execution can deadlock a worker under load. - Flows referenced by
subflow()in aSELECTorMULTISELECTinput expression appear in the parent flow’s Dependencies graph, the same way aSubflowtask reference does. - Only runs when an execution is created from the UI or the API, on the
WEBSERVERandSTANDALONEserver types. When aSubflowtask or a trigger starts the flow, the caller must pass a value for the input, and aSELECTorMULTISELECTinput must setallowCustomValue: trueso Kestra skips itsexpression. Otherwise, Kestra doesn’t create the execution, and the error asks you to pass the value from the caller. See Start the flow from a Subflow task or a trigger. - The default timeout is
PT1M. The hard cap isPT5M— passing a largertimeoutvalue is rejected at runtime. Both limits are configurable; see configuration reference. - Subflow recursion depth is capped at 3. A subflow whose own inputs call
subflow()counts against this limit. - Executions triggered by
subflow()carry asystem.from: subflowlabel automatically.
appLink()
Enterprise Edition’s appLink() builds links back to Kestra Apps:
{{ appLink(appId='com.example.my-app') }}{{ appLink(baseUrl=true) }}Use it in notifications when you want recipients to jump directly into the related app rather than the generic flow UI.
Was this page helpful?