Call Flows from Other Flows and Build Reusable Components
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.
Subflows let you build modular and reusable workflow components. Executing a subflow creates a new flow execution from within another flow.
Declaring a subflow
For example, you might define a subflow that handles error alerts by posting to Slack and email. Any flow that needs error notifications calls this subflow rather than duplicating those tasks.
How to declare a subflow
Use the io.kestra.plugin.core.flow.Subflow task and specify the flowId and namespace of the flow to execute. Pass custom inputs the same way you would pass arguments to a function.
The wait and transmitFailed properties control execution behavior. If wait is false, the parent flow continues without waiting for the subflow to finish. transmitFailed determines whether a failure in the subflow causes the parent flow to fail.
Recursive flows are not supported. A flow cannot call itself directly or indirectly; any cycle makes the flow invalid and risks infinite loops and unbounded fan-out.
Do instead: Use Loop and branching flowable tasks to iterate or split work without creating cycles (e.g., LoopUntil).
Practical example
id: critical_servicenamespace: company.team
tasks: - id: return_data type: io.kestra.plugin.jdbc.duckdb.Query sql: | INSTALL httpfs; LOAD httpfs; SELECT sum(total) as total, avg(quantity) as avg_quantity FROM read_csv_auto('https://huggingface.co/datasets/kestra/datasets/raw/main/csv/orders.csv', header=True); store: true
outputs: - id: some_output type: STRING value: "{{ outputs.return_data.uri }}"id: parent_servicenamespace: company.team
tasks: - id: subflow_call type: io.kestra.plugin.core.flow.Subflow namespace: company.team flowId: critical_service wait: true transmitFailed: true
- id: log_subflow_output type: io.kestra.plugin.scripts.shell.Commands taskRunner: type: io.kestra.plugin.core.runner.Process commands: - cat "{{ outputs.subflow_call.outputs.some_output }}"Subflow properties
io.kestra.plugin.core.flow.Subflow task properties:
| Field | Description |
|---|---|
flowId | The subflow’s identifier |
namespace | The namespace where the subflow is located |
inheritLabels | Determines if the subflow inherits labels from the parent (default: false). |
inputs | Inputs passed to the subflow |
labels | Labels assigned to the subflow |
outputs (deprecated) | Passes subflow outputs to the parent flow. Use flow outputs instead. |
revision | The subflow revision to execute (defaults to the latest) |
scheduleDate | Schedule subflow execution on a specific date rather than immediately. |
transmitFailed | If true, parent flow fails when the subflow execution fails or is killed (requires wait to be true; default: true). A cancelled subflow execution ends the Subflow task CANCELLED instead, and the parent continues. |
wait | If true, parent flow waits for subflow completion (default: true). |
Killing parent and subflow executions
When a parent execution waits on a subflow, the final state of each execution depends on which one you kill and how.
Killing the parent execution
Clicking Kill on a parent execution kills the parent and its running subflow executions. The dropdown next to Kill offers both options explicitly:
- Kill current and subflows kills the parent and its running subflow executions. The parent, the
Subflowtask run, and the subflow execution all endKILLED. - Kill current only kills the parent and lets the subflow execution finish. The parent stays
KILLINGuntil the subflow execution ends, then endsKILLED. No further tasks run in the parent.
Through the API, DELETE /api/v1/{tenant}/executions/{executionId}/actions/kill kills subflow executions by default. Add ?isOnKillCascade=false to kill the current execution only. The bulk kill endpoints, DELETE /api/v1/{tenant}/executions/kill/by-ids and DELETE /api/v1/{tenant}/executions/kill/by-query, never kill subflow executions.
Killing the subflow execution
When you kill a subflow execution directly, the parent treats it as a failure of the Subflow task, governed by transmitFailed and allowFailure:
Subflow task configuration | Subflow task run | Parent execution |
|---|---|---|
transmitFailed: true | KILLED | Skips the remaining tasks, runs any errors tasks, and ends FAILED. |
transmitFailed: true and allowFailure: true | WARNING | Continues with the next tasks and ends WARNING. |
transmitFailed: false | SUCCESS | Continues with the next tasks. |
A retry on the Subflow task does not restart a killed subflow execution.
Cancelled subflow executions
A subflow execution ends CANCELLED when the system cancels it, for example when the subflow’s concurrency limit is reached with behavior: CANCEL. Unlike a failed or killed subflow execution, a cancelled one does not stop the parent: the parent runs its next tasks either way. transmitFailed and allowFailure decide how the Subflow task run and the parent end:
Subflow task configuration | Subflow task run | Parent execution |
|---|---|---|
transmitFailed: true | CANCELLED | Continues with the next tasks, does not run errors tasks, and ends CANCELLED. |
transmitFailed: true and allowFailure: true | WARNING | Continues with the next tasks and ends WARNING. |
transmitFailed: true, allowFailure: true, and allowWarning: true | SUCCESS | Continues with the next tasks. |
transmitFailed: false | SUCCESS | Continues with the next tasks. |
When the Subflow task run ends CANCELLED, its logs include a warning: Subflow execution ... ends in CANCELLED state.
Before Kestra 2.0.9, a cancelled subflow execution left the Subflow task run SUCCESS whatever its configuration, so the parent ended SUCCESS. If your alerting or downstream triggers rely on that, review them after upgrading.
Passing data between parent and child flows
Any flow can emit outputs accessible to the parent flow. For more details and examples, see the Outputs page.
Accessing outputs from a subflow execution
Outputs include the execution ID, extracted outputs, and the final state (if wait is true). For example:
id: flow_outputsnamespace: company.team
tasks: - id: mytask type: io.kestra.plugin.core.output.OutputValues values: result: this is a task output used as a final flow output
outputs: - id: final type: STRING value: "{{ outputs.mytask.values.result }}"Access these outputs in the parent flow:
id: parent_flownamespace: company.team
tasks: - id: subflow type: io.kestra.plugin.core.flow.Subflow flowId: flow_outputs namespace: company.team wait: true
- id: log_subflow_output type: io.kestra.plugin.core.log.Log message: "{{ outputs.subflow.outputs.final }}"Passing inputs to a subflow
The example below passes a URL input from the parent flow into the subflow.
Subflow:
id: subflow_examplenamespace: company.team
inputs: - id: http_uri type: STRING
tasks: - id: download type: io.kestra.plugin.core.http.Request uri: "{{ inputs.http_uri }}"
- id: log type: io.kestra.plugin.core.log.Log message: "{{ outputs.download.body }}"
outputs: - id: data type: STRING value: "{{ outputs.download.body }}"Parent flow:
id: inputs_subflownamespace: company.team
inputs: - id: url type: STRING
tasks: - id: subflow type: io.kestra.plugin.core.flow.Subflow flowId: subflow_example namespace: company.team inputs: http_uri: "{{ inputs.url }}" wait: true
- id: hello type: io.kestra.plugin.core.log.Log message: "{{ outputs.subflow.outputs.data }}"Nested inputs
In the example below, the flow extracts JSON data from a REST API and passes it to a subflow as a nested input:
id: extract_jsonnamespace: company.team
tasks: - id: api type: io.kestra.plugin.core.http.Request uri: https://dummyjson.com/users
- id: read_json type: io.kestra.plugin.core.log.Log message: "{{ outputs.api.body }}"
- id: subflow type: io.kestra.plugin.core.flow.Subflow namespace: company.team flowId: subflow inputs: users.firstName: "{{ outputs.api.body | jq('.users') | first | first | jq('.firstName') | first }}" users.lastName: "{{ outputs.api.body | jq('.users') | first | first | jq('.lastName') | first }}" wait: true transmitFailed: trueNested inputs in the subflow definition add type validation to extracted JSON fields:
id: subflownamespace: company.team
inputs: - id: users.firstName type: STRING defaults: Rick
- id: users.lastName type: STRING defaults: Astley
tasks: - id: process_user_data type: io.kestra.plugin.core.log.Log message: hello {{ inputs.users }}You can then pass the entire users object, including nested fields, to any task in the subflow.
Was this page helpful?