Loop Node

Takes an array out of the payload and sends the branch below it one item at a time. Each pass carries its own position in the run, and when the last item comes back the node fires a second port — done — with everything the loop collected. It is the node for “for each row, do this”.

Two ports, not one

  • loop — fires once per item. Everything you want done per item hangs here.
  • done — fires once, after the last iteration, with the accumulated results. Summaries, notifications, and “write the report” hang here.
The branch under loop has to come back to the loop node for the next item to start. That return trip is what the iteration timeout below protects: a branch that never returns would otherwise park the loop forever.

What you configure

Iterable field

The field holding the array: data.items, or a template like {{payload.items}}. If it does not resolve to an array there is nothing to iterate and the loop finishes immediately.

Iteration timeout

5 to 300 seconds, 30 by default. If the loop-back signal has not arrived within it, the loop advances to the next item instead of waiting. Size it against the slowest thing in the branch — an HTTP call to a slow API, an approval that a human has to click.

On iteration error

PolicyWhat happensPick it when
TimeoutWaits out the timeout, then advancesThe default. A slow item is not a failed item.
SkipAdvances immediatelyBad rows are expected and the run should not drag.
RetryRe-executes the same item, up to Max retries per item (1–10), then skipsThe failure is transient — a rate limit, a flaky endpoint.
StopHalts the loopOrder matters and a gap would corrupt the result.
Retry does not stack on top of a node's own retries. Nodes with built-in retry — HTTP, Sheets and friends — that already exhausted theirs skip immediately instead of starting a second round. Otherwise a flaky endpoint would be hit retries × retries times.

What each port receives

Per iteration, on the loop port

jsonIteration payload
{
  "_meta": {
    "iterable": false,
    "count": 1,
    "loopIndex": 2,
    "loopTotal": 10,
    "isFirst": false,
    "isLast": false
  },
  "sku": "AB-99",
  "qty": 3
}

The item is spread at the top level, so the branch reads {{payload.sku}} and not {{payload.item.sku}}. An item that is not an object (a plain string, a number) arrives as { "value": … }.

loopIndex, loopTotal, isFirst and isLast are readable from templates — useful for “post a header on the first item” or “only notify on the last one”. Note that iterable stays false on purpose: one item is one object, so Filter, Limit and Aggregator downstream treat it as a single thing.

Once, on the done port

jsonDone payload
{
  "_meta": {
    "loopComplete": true,
    "totalIterations": 10,
    "iterable": true,
    "iterateField": "results",
    "count": 10
  },
  "results": [ … ]
}

results is marked iterable, so the done branch can itself fan out — to write a summary row per result, for instance. Compare count against totalIterations to notice items that were skipped.


Gotchas

  • The loop is sequential. One item at a time, by design — it is what makes the timeout and the error policy mean anything. For parallel fan-out, feed the array to nodes that accept an iterable payload directly.
  • Everything in the branch is billed per item. An AI Node or a Vector Store query inside a 500-item loop is 500 calls.
  • Plans cap the iterations — 50 on free, 5,000 on pro, 50,000 on enterprise. A longer array is truncated and reported, never rejected: the first N items run and the result says how many were left out. Refusing the whole job would mean a large but legitimate run never happens at all.
  • Nothing hanging off done means the result is discarded. The accumulated results array only exists on that port.