Skip to main content

Workflow.foreach()

The .foreach() method creates a loop that executes a step for each item in an array. It always returns an array containing the output from each iteration, preserving the original order.

Usage example
Direct link to Usage example

workflow.foreach(step1, { concurrency: 2 })

Parameters
Direct link to Parameters

step:

Step
The step instance to execute in the loop. The previous step must return an array type.

opts?:

object
Optional configuration for the loop. The concurrency option controls how many iterations can run in parallel (default: 1). The identity and display fields are preserved through serialization and storage and have no effect on execution.
number
string
string
Record<string, unknown>

Returns
Direct link to Returns

workflow:

Workflow
The workflow instance for method chaining. The output type is an array of the step's output type.

Behavior
Direct link to Behavior

Execution and waiting
Direct link to Execution and waiting

The .foreach() method processes every item before the next step executes, regardless of concurrency settings. The default concurrency: 1 processes items sequentially. Higher values use parallel batches, but the next step still waits for every batch to finish.

If you need to run multiple operations per item, use a nested workflow as the step. This keeps all operations for each item together and is cleaner than chaining multiple .foreach() calls. See Nested workflows inside foreach for examples.

Output structure
Direct link to Output structure

.foreach() always outputs an array. Each element in the output array corresponds to the result of processing the element at the same index in the input array.

// Input: [{ value: 1 }, { value: 2 }, { value: 3 }]
// Step adds 10 to each value
// Output: [{ value: 11 }, { value: 12 }, { value: 13 }]

Using .then() after .foreach()
Direct link to using-then-after-foreach

When you chain .then() after .foreach(), the next step receives the entire output array as its input. You can aggregate or process all results together.

workflow
.foreach(processItemStep) // Output: array of processed items
.then(aggregateStep) // Input: the entire array
.commit()

Using .map() after .foreach()
Direct link to using-map-after-foreach

Use .map() to transform the array output before passing it to the next step:

workflow
.foreach(processItemStep)
.map(async ({ inputData }) => ({
total: inputData.reduce((sum, item) => sum + item.value, 0),
count: inputData.length,
}))
.then(nextStep)
.commit()

Chaining multiple .foreach() calls
Direct link to chaining-multiple-foreach-calls

When you chain .foreach() calls, each operates on the array from the previous step:

workflow
.foreach(stepA) // If input is [a, b, c], output is [A, B, C]
.foreach(stepB) // Operates on [A, B, C], output is [A', B', C']
.commit()

If a step inside .foreach() returns an array, the output becomes an array of arrays. Use .map() with .flat() to flatten:

workflow
.foreach(chunkStep) // Output: [[chunk1, chunk2], [chunk3, chunk4]]
.map(async ({ inputData }) => inputData.flat()) // Output: [chunk1, chunk2, chunk3, chunk4]
.foreach(embedStep)
.commit()

Progress events during streaming
Direct link to Progress events during streaming

When using run.stream(), foreach steps emit a workflow-step-progress event after each iteration completes. This lets you track real-time progress without waiting for the entire foreach to finish.

const run = await workflow.createRun()
const stream = run.stream({ inputData })

for await (const chunk of stream) {
if (chunk.type === 'workflow-step-progress') {
console.log(`${chunk.payload.completedCount}/${chunk.payload.totalCount}`)
// e.g. "1/3", "2/3", "3/3"
}
}

Each progress event payload contains:

id:

string
The step ID of the foreach step

completedCount:

number
Number of iterations completed so far

totalCount:

number
Total number of iterations

currentIndex:

number
Index of the iteration that just completed

iterationStatus:

'success' | 'failed' | 'suspended'
Status of the iteration that just completed

iterationOutput?:

Record<string, any>
Output of the iteration (present when iterationStatus is 'success')

Resuming a single iteration
Direct link to Resuming a single iteration

When a step inside .foreach() suspends, each iteration suspends independently. Pass forEachIndex to run.resume() to resume one iteration at a time with its own resumeData. Omitting forEachIndex resumes every suspended iteration with the same data.

await run.resume({
step: 'approve',
resumeData: { ok: true },
forEachIndex: 1,
})