Skip to main content
Version: 2.0
For-Each Loop node interface

For-Each Loop node

For-Each Loop Node

Overview

The For-Each Loop turns a single array payload into multiple executions of downstream nodes — one run per item. Instead of building manual loops or complex branching, you define a single array expression, and the pipeline engine fans out the flow automatically. A companion End For-Each node marks the boundary of the loop body.

Typical use cases include:

  • Iterating over a list of work orders, sensor events, or measurements in a batch.
  • Running the same sub-pipeline for each line item in an order.
  • Breaking a large API response into per-item processing without writing imperative loop code.

How the Loop Works

The For-Each Loop uses a paired-node model: the For-Each node begins the loop and the End For-Each node marks where it ends. Every node between them is the loop body and executes once per array item, sequentially (item 0 completes fully before item 1 begins).

Execution Flow

              ┌────────────── Loop Body ──────────────┐
│ │
For-Each ──► [Node A] ──► [Node B] ──► End For-Each ──► [After Loop]
│ │
├── item 0 ──► A ──► B ──► End ─────────┤
├── item 1 ──► A ──► B ──► End ─────────┤
├── item 2 ──► A ──► B ──► End ─────────┤
│ │
└───── LoopResult (results, counts) ────┘
  1. The For-Each node evaluates the Source Array expression and determines the iteration count.
  2. For each item in the array, all loop body nodes execute in dependency order — from For-Each through to End For-Each.
  3. The End For-Each node acts as a pass-through that marks the loop boundary; its output for each iteration becomes that iteration's result.
  4. After all iterations complete (or the loop stops early due to errors), the For-Each node produces a LoopResult object and execution continues with nodes connected after the End For-Each node.
Auto-Pairing

When you drag a For-Each node onto the canvas, an End For-Each node is automatically created and connected 400 px to the right. You only need to place your processing nodes between them.


Loop Variables

Inside the loop body, the following expression variables are available:

VariableTypeDescription
$itemanyThe current array element being processed (always refers to the innermost loop).
$indexnumberZero-based index of the current iteration (0, 1, 2, …) for the innermost loop.

These variables are scoped to the loop body and cleared after the loop completes.

Named Loop Variables (Item Variable Name)

If you set the optional Item Variable Name parameter (e.g., order), two additional variables become available:

VariableTypeDescription
$orderanyAlias for the current item — identical to $item but named for clarity.
$orderIndexnumberAlias for the current index — identical to $index but named.

Named variables are particularly useful for readability and self-documenting pipelines. The generic $item / $index always refer to the innermost loop, while named variables persist by name.

Example — accessing variables in a Set or Code node inside the loop:

{{ $item.name }}         — property of the current item
{{ $item }} — the entire current item
{{ $index }} — current iteration index (0-based)
{{ $order.id }} — same as $item.id (when itemName = "order")
{{ $orderIndex }} — same as $index (when itemName = "order")

Configuration Reference

For-Each Node Parameters

For-Each configuration panel

For-Each configuration panel

Source Array (required)

TypeExpression (string)
Default"" (empty)
Placeholder{{ $node["Data"].items }}

The expression that resolves to the array to iterate over. This is the only required parameter. If the expression does not resolve to an array, the node will error.

Expression examples:

ExpressionDescription
{{ $node["API"].data }}Array from an API response node
{{ $node["Query"].items }}Extract the items field from a query result
{{ $node["Data"].records }}Array from a data node
{{ $trigger.payload }}Array from the trigger's payload directly
{{ filter($node["Orders"].list, #.status == "active") }}Filtered subset using a built-in function

Item Variable Name (optional)

TypeText (alphanumeric + underscore only)
Default"" (empty — uses $item / $index only)
Max Length30 characters

Give the loop item a descriptive name. When set to e.g. order, the variables $order and $orderIndex become available in addition to $item and $index. See Named Loop Variables above.

Error Mode

TypeSelect
DefaultstopOnFirst

Controls how the loop handles failures during iteration.

ValueLabelBehavior
stopOnFirstStop on First ErrorStops the loop immediately when any iteration fails. The entire For-Each node is marked as failed. This is the default and safest option.
continueContinue on ErrorSkips the failed item and continues with the remaining items. Failed items are recorded in the output's errors array.
continueWithRetryContinue with RetryRetries the failed item with exponential backoff before skipping it. If all retries fail, records the error and continues to the next item. See Item Retry Configuration below.
collectAllCollect All ErrorsProcesses every item regardless of failures and collects all errors at the end. Useful for audit or reporting scenarios where you need a full picture.

Error Thresholds

These fields appear when Error Mode is set to anything other than Stop on First Error:

ParameterTypeDefaultRangeDescription
Max Error Countnumber00–1,000Maximum number of errors before stopping the loop early. 0 means unlimited — the loop will process all items.
Error Threshold (%)number00–100Stop the loop if the error percentage exceeds this threshold. For example, 50 means stop if more than half the processed items have failed. 0 disables threshold checking.
Partial Resultstoggletrue (on)When enabled, the loop outputs all successful results collected so far even if the loop stopped early due to errors. When disabled, early stops produce no result data.

Result Mode

TypeSelect
Defaultnone

Controls what iteration results are collected in the output.

ValueLabelMemoryDescription
noneNo ResultsO(1)Fire-and-forget — only tracks count, successCount, and errorCount. Best for side-effect loops (e.g., sending notifications). This is the default.
errorsOnlyErrors OnlyO(errors)Only tracks failed items in the errors array. Useful for debugging without the overhead of collecting all results.
lastKeep Last OnlyO(1)Only keeps the last successful iteration's result. Useful when only the final computation matters.
allCollect All ResultsO(n)Stores every iteration's result in the results array. Required when downstream nodes need the full set of processed data.
Memory usage

Collect All Results stores every iteration's output in memory. For large arrays (1,000+ items), prefer No Results, Errors Only, or Keep Last Only to avoid excessive memory usage.

Item Retry Configuration

When Error Mode is set to Continue with Retry, the loop retries each failed item using exponential backoff before moving on. The retry parameters are:

FieldTypeDefaultRangeDescription
Max Attemptsnumber31–10Maximum retry attempts per failed item.
Initial Delay (ms)number1,000100–30,000Wait time before the first retry.
Max Delay (ms)number120,0001,000–300,000Upper bound for the backoff delay.
Multipliernumber2.01.0–5.0Exponential backoff multiplier. Each retry waits initialDelay × multiplier^attempt.
Jitter Factornumber0.10–0.5Adds randomness to avoid thundering-herd retries.

Only transient errors (network timeouts, connection resets, rate limits, 503/429 responses) are retried. Permanent errors (invalid input, authentication failures, 400/401/403/404 responses) skip remaining retries immediately.

Execution Settings

Both For-Each and End For-Each nodes have an Execution Settings tab with standard options:

SettingOptionsDefaultDescription
Timeout (seconds)numberPipeline defaultMaximum execution time for the node (1–600).
Retry on TimeoutPipeline Default / Enabled / DisabledPipeline DefaultWhether to retry the node-level execution on timeout.
Retry on FailPipeline Default / Enabled / DisabledPipeline DefaultWhether to retry the node-level execution on failure. When Enabled, shows Advanced Retry Configuration.
On ErrorPipeline Default / Stop Pipeline / Continue ExecutionPipeline DefaultBehavior when the node fails after all retries.
note

The Execution Settings apply to the For-Each node itself (e.g., overall loop timeout), not to individual iterations. For per-item retry behavior, use the Error Mode set to Continue with Retry and configure the item retry parameters in the Configuration tab.


End For-Each Node

End For-Each node interface

End For-Each node

The End For-Each node marks the end of the loop body. It has no configurable parameters beyond a name and the standard Execution Settings.

Behavior

  • Pass-through: The End For-Each node receives output from the last node in the loop body and passes it through unchanged. Its output for each iteration becomes that iteration's result.
  • Loop metadata: When executing inside a loop, the End For-Each node adds _loop metadata to its output:
    {
    "_loop": {
    "index": 2,
    "total": 5
    },
    "sensorId": "S-3",
    "value": 9.8
    }
    The _loop metadata is merged into whatever data the upstream node produced.
  • Boundary marker: The pipeline engine uses the End For-Each node to determine which nodes belong to the loop body vs. which nodes run after the loop.

How to Set Up

  1. Add a For-Each node to start the loop (this auto-creates the End For-Each).
  2. Place your processing nodes between For-Each and End For-Each.
  3. Connect the last processing node to End For-Each.
  4. Connect End For-Each's output to any nodes that should run after the entire loop finishes.
For-Each ──► [Process A] ──► [Process B] ──► End For-Each ──► [Post-Loop Node]

Output Structure (LoopResult)

After all iterations complete, the For-Each node produces a LoopResult object that is passed to nodes connected after the End For-Each. This is the data available to downstream nodes via expressions like {{ $node["For-Each"].results }}.

Successful run (no errors, Result Mode = Collect All Results):

{
"results": [
{
"_loop": { "index": 0, "total": 2 },
"name": "Alice",
"processed": true
},
{
"_loop": { "index": 1, "total": 2 },
"name": "Bob",
"processed": true
}
],
"count": 2,
"successCount": 2,
"errorCount": 0,
"partial": false
}

Run with errors (Error Mode = Continue, Result Mode = Collect All Results):

{
"results": [
{
"_loop": { "index": 0, "total": 3 },
"name": "Alice",
"processed": true
}
],
"count": 3,
"successCount": 1,
"errorCount": 2,
"errors": [
{
"index": 1,
"item": { "name": "Bob" },
"error": "Connection timeout",
"code": "CONN_TIMEOUT",
"attemptCount": 3
},
{
"index": 2,
"item": { "name": "Charlie" },
"error": "Service unavailable",
"code": "SERVICE_UNAVAILABLE",
"attemptCount": 1
}
],
"partial": false
}
note

Each entry in the results array includes _loop metadata (index and total) added by the End For-Each node. This tells you which iteration produced each result.

Field Reference

FieldTypePresentDescription
resultsarrayAlwaysIteration outputs collected from the End For-Each node. Each entry includes _loop metadata. Contents depend on Result Mode: empty [] for none and errorsOnly, single item for last, all items for all.
countnumberAlwaysTotal number of items in the source array (attempted iterations).
successCountnumberAlwaysNumber of iterations that completed successfully.
errorCountnumberAlwaysNumber of iterations that failed.
partialbooleanAlwaystrue if the loop stopped early (due to error thresholds or max error count) before processing all items.
errorsarrayOnly when errors occurDetails of each failed iteration. Each entry contains index, error message, error code, attemptCount, and optionally the original item. Omitted entirely when there are no errors.
stopReasonstringOnly on early stopReason the loop stopped early (e.g., "max error count reached"). Omitted entirely when the loop completes normally.

Accessing Results in Downstream Nodes

After the loop, you can reference the results in any downstream node:

{{ $node["For-Each"].results }}              — full results array
{{ $node["For-Each"].results[0].name }} — first result's name field
{{ $node["For-Each"].results[0]._loop }} — loop metadata of first result
{{ $node["For-Each"].count }} — total items attempted
{{ $node["For-Each"].successCount }} — successful iterations
{{ $node["For-Each"].errorCount }} — failed iterations
{{ $node["For-Each"].partial }} — whether results are incomplete
{{ $node["For-Each"].errors }} — error details (only if errors occurred)
{{ $node["For-Each"].stopReason }} — early stop reason (only if stopped early)

Restrictions and Validations

Blocked Node Types

The following node types cannot be placed inside a For-Each loop body. The pipeline engine enforces this by auto-disabling the pipeline on save and blocking it from being enabled.

NodeTypeWhy It's Blocked
Buffertransform.bufferAccumulates data until a count or time threshold is reached. Inside a loop, each iteration provides only one item, so the buffer may never flush — causing the loop to hang indefinitely.
Aggregatortransform.aggregatorSimilar to Buffer — aggregates data across multiple inputs until a window closes. A single loop iteration cannot satisfy the aggregation window, so the loop body would never complete.
warning

The restriction is enforced at the backend level. You can still drag these nodes into the loop body on the canvas, but:

  • A validation warning appears immediately on the node.
  • If the pipeline was enabled, it is automatically disabled when you save.
  • You cannot re-enable the pipeline until the node is moved outside the loop body.

Nested For-Each Loops Are Not Supported

For-Each loops cannot be nested inside other For-Each loops. The pipeline engine enforces this at multiple levels:

  • On the canvas: Attempting to connect a For-Each node inside the body of another For-Each shows an error toast and blocks the connection.
  • On save: If nested loops are detected, the pipeline is automatically disabled and a validation error is shown.
  • On enable: Pipelines with nested loops cannot be enabled.

If you need nested iteration, consider flattening the data structure first using a Code or Set node, or splitting the work across separate pipelines.

For-Each Must Have a Matching End For-Each

Every For-Each node requires a corresponding End For-Each node connected downstream. If the engine cannot find a matching End For-Each at runtime (via graph traversal), the For-Each node fails with an error. The End For-Each is auto-created when you add a For-Each node, so this only occurs if you manually delete the End For-Each or disconnect it entirely.

Disconnected Loop Body Nodes

If a node is connected to the For-Each node but has no path to the End For-Each node, it is flagged as a warning (not an error). These nodes will not execute during the loop — they are effectively orphaned within the loop body. The pipeline editor shows a visual warning for these nodes.

Source Array Is Required

The Source Array expression is the only required parameter. Saving the pipeline with an empty Source Array produces a validation error on the For-Each node.

What IS Allowed Inside a For-Each Loop

All other node types work normally inside a For-Each loop body, including:

NodeNotes
ConditionFully supported — branch routing (True/False outputs) works correctly per iteration. If a condition routes to the False branch, only that branch's nodes are skipped for that iteration; the next iteration re-evaluates fresh.
SetUse $item / $index (or named variables) to transform per-item data.
JavaScript (Code)Full access to loop variables. Executes independently per iteration.
UNS PublishSupports dynamic topics using expressions like {{ keys($item)[0] }}.
REST / HTTPMake per-item API calls. Combine with Continue with Retry error mode for resilience.
Database nodes (PostgreSQL, MSSQL, Oracle, MongoDB)Execute per-item queries.
Industrial nodes (Modbus, OPC UA, S7, etc.)Read/write per-item tags.
MergeCan merge branches within the loop body.
WaitIntroduces a delay per iteration.
CounterWorks inside ForEach but note that counter state persists across iterations within the same execution.
Data SerializerWorks inside ForEach.
Data InstanceMap per-item data to a data model.
Group ByGroup per-item fields.
No-OpPass-through, works normally.

Usage Examples

Example 1: Process Each User From an API

A REST node named "Get Users" returns:

{
"users": [
{ "id": "U-1", "name": "Alice", "email": "alice@example.com" },
{ "id": "U-2", "name": "Bob", "email": "bob@example.com" }
]
}

For-Each configuration:

FieldValue
Source Array{{ $node["Get Users"].users }}
Error ModeStop on First Error
Result ModeNo Results

Inside the loop body, use a Set or Code node to access each user:

{{ $item.name }}    → "Alice" (iteration 0), "Bob" (iteration 1)
{{ $item.email }} → "alice@example.com", "bob@example.com"
{{ $index }} → 0, 1

This is a fire-and-forget pattern — each user is processed independently and no results are collected.

Example 2: Transform Each Item With a JavaScript Node

An upstream node returns sensor readings:

{
"readings": [
{ "sensorId": "S-1", "value": 10.2, "unit": "°C" },
{ "sensorId": "S-2", "value": 11.5, "unit": "°C" },
{ "sensorId": "S-3", "value": 9.8, "unit": "°C" }
]
}

Pipeline structure:

[Read Sensors] ──► [For-Each] ──► [Convert Temps] ──► [End For-Each] ──► [Send Report]

For-Each configuration:

FieldValue
Source Array{{ $node["Read Sensors"].readings }}
Item Variable Namereading
Error ModeContinue on Error
Result ModeCollect All Results
Max Error Count0 (unlimited)
Partial ResultsOn

Inside the loop body, a JavaScript node named "Convert Temps" transforms each reading. The code receives $reading (the named loop variable) and returns a new object:

const fahrenheit = $reading.value * 9/5 + 32;
return {
sensorId: $reading.sensorId,
celsius: $reading.value,
fahrenheit: Math.round(fahrenheit * 100) / 100
};

Because Result Mode is set to Collect All Results, every iteration's output from the End For-Each node is collected into the results array. After the loop, a downstream node accesses the converted data:

{{ $node["For-Each"].results }}
→ [
{ "sensorId": "S-1", "celsius": 10.2, "fahrenheit": 50.36 },
{ "sensorId": "S-2", "celsius": 11.5, "fahrenheit": 52.70 },
{ "sensorId": "S-3", "celsius": 9.8, "fahrenheit": 49.64 }
]

Example 3: Resilient Batch Processing with Retry

An order management system returns work orders to process:

{
"orders": [
{ "id": "WO-101", "type": "maintenance" },
{ "id": "WO-102", "type": "inspection" },
{ "id": "WO-103", "type": "maintenance" },
{ "id": "WO-104", "type": "repair" }
]
}

For-Each configuration:

FieldValue
Source Array{{ $node["Get Orders"].orders }}
Item Variable Nameorder
Error ModeContinue with Retry
Result ModeErrors Only
Max Error Count5
Error Threshold (%)50
Partial ResultsOn

Inside the loop body, each order is sent to an external API. If an API call fails due to a transient error (timeout, rate limit), the loop retries it with exponential backoff. If the retry also fails, the error is recorded and the loop moves to the next order. The loop stops early if more than 5 errors occur or if more than 50% of processed items have failed.

After the loop, the output contains:

{{ $node["For-Each"].errorCount }}    → number of failed orders
{{ $node["For-Each"].errors }} → details of each failure
{{ $node["For-Each"].partial }} → true if loop stopped early

Example 4: Filter Before Iteration

You can use expression functions to filter the source array before iterating:

For-Each configuration:

FieldValue
Source Array{{ filter($node["Get Orders"].orders, #.status == "active") }}
Error ModeStop on First Error
Result ModeNo Results

Only orders with status == "active" are iterated — closed or pending orders are excluded entirely.

Example 5: Dynamic-Key Objects (e.g., Connector Read Results)

Some upstream nodes — particularly industrial connectors like Modbus, OPC UA, or S7 — return arrays where each item is an object with a single dynamic key (the tag or register name) rather than a fixed property like name or id:

[
{"Energy_KWh": {"success": true, "value": 124299, "timestamp": "2026-02-16T08:04:53Z"}},
{"Coolant_Temp": {"success": true, "value": 218, "timestamp": "2026-02-16T08:04:53Z"}},
{"Spindle_Speed": {"success": true, "value": 0, "timestamp": "2026-02-16T08:04:53Z"}}
]

Here, $item.name will not work because there is no name property — the tag name is the object key. Use the keys() and values() built-in functions to extract them.

For-Each configuration:

FieldValue
Source Array{{ $node["Read Modbus"].data }}
Item Variable NamesensorItem
Error ModeContinue on Error
Result ModeNo Results

Extracting the key and value inside the loop body:

{{ keys($sensorItem)[0] }}           → "Energy_KWh", "Coolant_Temp", "Spindle_Speed"
{{ values($sensorItem)[0] }} → the full read-result object for each tag
{{ values($sensorItem)[0].value }} → 124299, 218, 0
{{ values($sensorItem)[0].success }} → true, true, true

Using the tag name in a dynamic UNS topic (in a UNS Publish node inside the loop):

FieldExpression
Topicenterprise/site1/area1/{{ keys($sensorItem)[0] }}
Value{{ values($sensorItem)[0].value }}

This publishes each tag to its own UNS topic: enterprise/site1/area1/Energy_KWh, enterprise/site1/area1/Coolant_Temp, etc.

note

keys() returns keys sorted alphabetically. When each object has only one key (as in the example above), this has no effect. For multi-key objects, use keys(obj)[0] with caution and consider restructuring the data first.

Example 6: Post-Loop Aggregation (Sum, Average, Metrics)

A common pattern is to use For-Each to transform each item, collect all results, and then compute aggregate metrics (totals, averages, counts) in a downstream node after the loop.

Scenario: A production line has multiple stations. An upstream node named "Get Stations" returns:

{
"stations": [
{ "id": "Station-1", "partsProduced": 150, "partsGood": 145, "partsBad": 5, "energyKWh": 45.2 },
{ "id": "Station-2", "partsProduced": 200, "partsGood": 198, "partsBad": 2, "energyKWh": 62.1 },
{ "id": "Station-3", "partsProduced": 180, "partsGood": 170, "partsBad": 10, "energyKWh": 53.8 },
{ "id": "Station-4", "partsProduced": 220, "partsGood": 215, "partsBad": 5, "energyKWh": 68.4 }
]
}

Pipeline structure:

[Get Stations] ──► [For-Each] ──► [Compute Quality] ──► [End For-Each] ──► [Aggregate] ──► [Publish Report]

Step 1 — For-Each configuration:

FieldValue
Source Array{{ $node["Get Stations"].stations }}
Item Variable Namestation
Error ModeContinue on Error
Result ModeCollect All Results

Step 2 — Inside the loop: JavaScript node "Compute Quality"

This node runs once per station. It calculates the quality rate and energy efficiency for each station:

const qualityRate = ($station.partsGood / $station.partsProduced) * 100;
const energyPerPart = $station.energyKWh / $station.partsProduced;

return {
id: $station.id,
partsProduced: $station.partsProduced,
partsGood: $station.partsGood,
partsBad: $station.partsBad,
qualityRate: Math.round(qualityRate * 100) / 100,
energyKWh: $station.energyKWh,
energyPerPart: Math.round(energyPerPart * 10000) / 10000
};

After all 4 iterations, the For-Each output results array contains:

[
{ "id": "Station-1", "partsProduced": 150, "partsGood": 145, "partsBad": 5, "qualityRate": 96.67, "energyKWh": 45.2, "energyPerPart": 0.3013 },
{ "id": "Station-2", "partsProduced": 200, "partsGood": 198, "partsBad": 2, "qualityRate": 99.00, "energyKWh": 62.1, "energyPerPart": 0.3105 },
{ "id": "Station-3", "partsProduced": 180, "partsGood": 170, "partsBad": 10, "qualityRate": 94.44, "energyKWh": 53.8, "energyPerPart": 0.2989 },
{ "id": "Station-4", "partsProduced": 220, "partsGood": 215, "partsBad": 5, "qualityRate": 97.73, "energyKWh": 68.4, "energyPerPart": 0.3109 }
]

Step 3 — After the loop: Set node "Aggregate"

A Set node computes summary metrics using expression functions on the collected results. Each field uses the results array from the For-Each node:

FieldExpressionResult
totalPartsProduced{{ sum(pluck($node["For-Each"].results, "partsProduced")) }}750
totalPartsGood{{ sum(pluck($node["For-Each"].results, "partsGood")) }}728
totalPartsBad{{ sum(pluck($node["For-Each"].results, "partsBad")) }}22
avgQualityRate{{ round(avg(pluck($node["For-Each"].results, "qualityRate")), 2) }}96.96
totalEnergyKWh{{ round(sum(pluck($node["For-Each"].results, "energyKWh")), 2) }}229.5
stationCount{{ len($node["For-Each"].results) }}4
belowTarget{{ filter($node["For-Each"].results, #.qualityRate < 95) }}[{"id": "Station-3", ...}]
belowTargetCount{{ count($node["For-Each"].results, #.qualityRate < 95) }}1
bestStation{{ first(sortDesc(pluck($node["For-Each"].results, "qualityRate"))) }}99.00
worstStation{{ first(sortAsc(pluck($node["For-Each"].results, "qualityRate"))) }}94.44

The Aggregate node output is a single summary object:

{
"totalPartsProduced": 750,
"totalPartsGood": 728,
"totalPartsBad": 22,
"avgQualityRate": 96.96,
"totalEnergyKWh": 229.5,
"stationCount": 4,
"belowTarget": [{ "id": "Station-3", "qualityRate": 94.44 }],
"belowTargetCount": 1,
"bestStation": 99.00,
"worstStation": 94.44
}

Key expression patterns used:

PatternWhat It Does
pluck(arr, "field")Extracts a single field from every object in the array, producing a flat array of values.
sum(pluck(...))Combines pluck with sum to total a specific field across all results.
avg(pluck(...))Average of a specific field.
round(value, decimals)Rounds the result to avoid floating-point noise.
filter(arr, #.field < threshold)Filters results that match a condition (e.g., stations below quality target).
count(arr, #.field < threshold)Counts how many results match a condition.
sortAsc(pluck(...)) / sortDesc(...)Sorts values to find min/max. Combined with first() to extract the top or bottom value.
When to use this pattern

Use Collect All Results + post-loop aggregation when you need summary statistics from the loop. This is the equivalent of a SQL GROUP BY — the loop body transforms/enriches each row, and the downstream node computes the rollup. For very large arrays (10,000+ items), consider using a JavaScript node after the loop instead of many individual Set expressions, to avoid repeated iteration over the results array.


Pipeline Structure Diagram

The following diagram shows a typical For-Each pipeline structure:

Trigger ──► Get Data ──► For-Each ──► Process (per item) ──► End For-Each ──► Send Report
│ └── loop body ──────────┘ │
│ │
└──────── LoopResult (after all iterations) ────────┘
  • Trigger → starts the pipeline.
  • Get Data → fetches the array (e.g., from an API, database, or connector).
  • For-Each → evaluates the Source Array expression and begins iteration.
  • Process (per item) → any number of nodes between For-Each and End For-Each (the loop body). These execute once per item.
  • End For-Each → marks the end of the loop body; collects each iteration's output.
  • Send Report → runs once after all iterations complete, receiving the LoopResult.

When to Use For-Each Loop

Use the For-Each Loop node when:

  • You receive arrays from connectors or upstream transforms and want each element to drive a separate execution of downstream logic.
  • You need a clear, declarative way to pick which collection to iterate — without hand-rolling loops in Code nodes.
  • You want iteration to integrate cleanly with other nodes like Data Instance, Set, and Code, using $item and $index for per-element access.
  • You need error resilience — continue processing remaining items even if some fail, with configurable retry and threshold logic.
Best practices
  • Always pair a For-Each node with an End For-Each node. The pipeline engine uses End For-Each to determine which nodes belong to the loop body.
  • Use named variables (itemName) for clarity, especially when the generic $item could be ambiguous.
  • Choose the right Result Mode — avoid all for large arrays unless downstream nodes actually need every result.
  • Set error thresholds when using Continue or Collect All modes to prevent runaway failures from processing thousands of items.