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 .md to any kestra.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:

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_basic
namespace: 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_nested
namespace: 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_outputs
namespace: 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_parallel
namespace: 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_item
namespace: 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_out
namespace: 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

Was this page helpful?