Iterate Over Lists with the Loop Task
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.
Use the Loop task to iterate over a list of values and run tasks for each item. Each iteration runs as an isolated sub-execution with access to the current value via item.value and the zero-based index via item.index.
Prerequisites
Before you begin:
- Deploy Kestra in your preferred development environment.
- Ensure you have a basic understanding of how to run Kestra flows.
Basic iteration
The simplest use of Loop iterates over a static list and runs child tasks for each item. The example below makes an API call for each author in the list.
id: loop_basicnamespace: company.team
tasks: - id: loop type: io.kestra.plugin.core.flow.Loop values: ["pynchon", "dostoyevsky", "hedayat"] tasks: - id: api type: io.kestra.plugin.core.http.Request uri: "https://openlibrary.org/search.json?author={{ item.value }}&sort=new"Inside each iteration:
{{ item.value }}— the current value from the list{{ item.index }}— the zero-based position (0, 1, 2, …)
After execution, the Gantt view shows a separate task group for each author.
When values contains objects, each item.value is a JSON string. Use fromJson(item.value).field to access fields — item.value.field does not work.
Nested loops
To iterate over multiple dimensions, nest Loop tasks. The inner loop accesses the outer loop’s value with {{ item.parent.value }}. For three or more levels, {{ item.parents[1].value }} is the grandparent — item.parents[0] is the same as item.parent.
id: loop_nestednamespace: company.team
tasks: - id: outer type: io.kestra.plugin.core.flow.Loop values: ["bucket1", "bucket2"] tasks: - id: inner type: io.kestra.plugin.core.flow.Loop values: [2025, 2026] tasks: - id: log type: io.kestra.plugin.core.log.Log message: "bucket={{ item.parent.value }} year={{ item.value }}"Collect outputs across iterations
By default, outputs produced inside a loop are not visible to tasks that run after it. Declare an outputs: block on the Loop task to surface values explicitly. After the loop, outputs.loop.outputs is a list of per-iteration results. Use loopOutputs() to extract one field across all iterations as a flat list.
id: loop_outputsnamespace: company.team
tasks: - id: loop type: io.kestra.plugin.core.flow.Loop values: ["alpha", "beta", "gamma"] fetchType: AUTO outputs: - id: label type: STRING value: "{{ outputs.process.value }}" tasks: - id: process type: io.kestra.plugin.core.debug.Return format: "processed {{ item.value }}"
- id: read_outputs type: io.kestra.plugin.core.log.Log message: "All results: {{ loopOutputs(outputs.loop.outputs, 'label') }}"Run iterations in parallel
Set concurrencyLimit to a positive integer to cap how many iterations run at once. Setting it to 0 removes the cap entirely — only do this for small datasets where you understand the resource implications.
id: loop_parallelnamespace: company.team
tasks: - id: loop type: io.kestra.plugin.core.flow.Loop values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] concurrencyLimit: 0 tasks: - id: parallel type: io.kestra.plugin.core.flow.Parallel tasks: - id: log type: io.kestra.plugin.core.log.Log message: "Processing {{ item.value }}" - id: shell type: io.kestra.plugin.scripts.shell.Commands commands: - "echo done {{ item.value }}"Fan out with subflows
Use Loop with Subflow to launch an isolated child execution per iteration. Each subflow gets its own retry policy, logs, and failure state — useful when you want per-item isolation rather than running everything inside a single parent execution.
The subflow to call per iteration:
id: process_itemnamespace: company.team
inputs: - id: item type: STRING
tasks: - id: log type: io.kestra.plugin.core.log.Log message: "Processing: {{ inputs.item }}"The parent flow that queries a dataset and fans out one subflow per row:
id: fan_outnamespace: company.team
tasks: - id: extract type: io.kestra.plugin.jdbc.duckdb.Query sql: | INSTALL httpfs; LOAD httpfs; SELECT * FROM read_csv_auto('https://huggingface.co/datasets/kestra/datasets/raw/main/csv/orders.csv', header=True); store: true
- id: loop type: io.kestra.plugin.core.flow.Loop values: "{{ outputs.extract.uri }}" tasks: - id: process type: io.kestra.plugin.core.flow.Subflow namespace: company.team flowId: process_item wait: true transmitFailed: true inputs: item: "{{ item.value }}"Set wait: true so the parent tracks each child’s outcome. Set transmitFailed: true to fail the loop if any subflow fails. Combine with concurrencyLimit on the Loop task to cap how many subflows run simultaneously.
Next steps
- For the full Loop property reference, see the Loop task documentation.
- For output collection patterns, error handling, and map-reduce examples, see the Flowable Tasks reference.
- For Loop best practices, see the Loop best practices guide.
Was this page helpful?