Execute a group of tasks for each value in the list.

You can control how many task groups are executed concurrently by setting the concurrencyLimit property.

  • If you set the concurrencyLimit property to 0, Kestra will execute all task groups concurrently for all values.
  • If you set the concurrencyLimit property to 1, Kestra will execute each task group one after the other starting with the task group for the first value in the list.

Regardless of the concurrencyLimit property, the tasks will run one after the other — to run those in parallel, wrap them in a Parallel task as shown in the last example below (see the flow parallel_tasks_example).

The values should be defined as a JSON string or an array, e.g. a list of string values ["value1", "value2"] or a list of key-value pairs [{"key": "value1"}, {"key": "value2"}].

You can access the current iteration value using the variable {{ taskrun.value }} or {{ parent.taskrun.value }} if you are in a nested child task. You can access the batch or iteration number with {{ taskrun.iteration }}.

If you need to execute more than 2-5 tasks for each value, we recommend triggering a subflow for each value for better performance and modularity. Check the flow best practices documentation for more details.

yaml
type: "io.kestra.plugin.core.flow.foreach"

The {{ taskrun.value }} from the for_each task is available only to direct child tasks such as the before_if and the if tasks. To access the taskrun value of the parent task in a nested child task such as the after_if task, use {{ parent.taskrun.value }}.

yaml
id: for_loop_example
namespace: company.team

tasks:
  - id: for_each
    type: io.kestra.plugin.core.flow.ForEach
    values: ["value 1", "value 2", "value 3"]
    tasks:
      - id: before_if
        type: io.kestra.plugin.core.debug.Return
        format: "Before if {{ taskrun.value }}"
      - id: if
        type: io.kestra.plugin.core.flow.If
        condition: '{{ taskrun.value == "value 2" }}'
        then:
          - id: after_if
            type: io.kestra.plugin.core.debug.Return
            format: "After if {{ parent.taskrun.value }}"

This flow uses YAML-style array for values. The task for_each iterates over a list of values and executes the return child task for each value. The concurrencyLimit property is set to 2, so the return task will run concurrently for the first two values in the list at first. The return task will run for the next two values only after the task runs for the first two values have completed.

yaml
id: for_each_value
namespace: company.team

tasks:
  - id: for_each
    type: io.kestra.plugin.core.flow.ForEach
    values:
      - value 1
      - value 2
      - value 3
      - value 4
    concurrencyLimit: 2
    tasks:
      - id: return
        type: io.kestra.plugin.core.debug.Return
        format: "{{ task.id }} with value {{ taskrun.value }}"

This example shows how to run tasks in parallel for each value in the list. All child tasks of the parallel task will run in parallel. However, due to the concurrencyLimit property set to 2, only two parallel task groups will run at any given time.

yaml
id: parallel_tasks_example
namespace: company.team

tasks:
  - id: for_each
    type: io.kestra.plugin.core.flow.ForEach
    values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    concurrencyLimit: 2
    tasks:
      - id: parallel
        type: io.kestra.plugin.core.flow.Parallel
        tasks:
        - id: log
          type: io.kestra.plugin.core.log.Log
          message: Processing {{ parent.taskrun.value }}
        - id: shell
          type: io.kestra.plugin.scripts.shell.Commands
          commands:
            - sleep {{ parent.taskrun.value }}

This example demonstrates processing data across nested loops of S3 buckets, years, and months. It generates structured identifiers (e.g., bucket1_2025_March) by combining values from each loop level, while accessing parent loop values like years and buckets, which can be useful for partitioned storage paths or time-based datasets. The flow uses dynamic expressions referencing parent context.

yaml
id: loop_multiple_times
namespace: company.team

inputs:
  - id: s3_buckets
    type: ARRAY
    itemType: STRING
    defaults:
      - bucket1
      - bucket2

  - id: years
    type: ARRAY
    itemType: INT
    defaults:
      - 2025
      - 2026

  - id: months
    type: ARRAY
    itemType: STRING
    defaults:
      - March
      - April

tasks:
  - id: buckets
    type: io.kestra.plugin.core.flow.ForEach
    values: "{{inputs.s3_buckets}}"
    tasks:
      - id: year
        type: io.kestra.plugin.core.flow.ForEach
        values: "{{inputs.years}}"
        tasks:
          - id: month
            type: io.kestra.plugin.core.flow.ForEach
            values: "{{inputs.months}}"
            tasks:
              - id: full_table_name
                type: io.kestra.plugin.core.log.Log
                message: |
                  Full table name: {{parents[1].taskrun.value }}_{{parent.taskrun.value}}_{{taskrun.value}}
                  Direct/current loop (months): {{taskrun.value}}
                  Value of loop one higher up (years): {{parents[0].taskrun.value}}
                  Further up (table types): {{parents[1].taskrun.value}}
Properties
SubType
Min items 1

The list of values for which Kestra will execute a group of tasks.

The values can be passed as a string, a list of strings, or a list of objects.

Default 1
Minimum >= 0

The number of concurrent task groups for each value in the values array.

If you set the concurrencyLimit property to 0, Kestra will execute all task groups concurrently for all values (zero limits!).

If you set the concurrencyLimit property to 1, Kestra will execute each task group one after the other starting with the first value in the list (limit concurrency to one task group that can be actively running at any time).

SubType

List of tasks to run if any tasks failed on this FlowableTask.

SubType