For the complete documentation index, see llms.txt. Any page on this site can be fetched as Markdown by appending .md to its URL.

Loops

Run a block of nodes once per element of an array with Loop, Loop Group, and Loop Break.

A loop walks a JSON array one element at a time and runs a block of nodes for each one. Before every pass it writes the current element to a variable you name, so the body reads the element with the same $(name) syntax as any other variable.

There are two forms. They iterate identically: the difference is only how control gets back to the loop node for the next element.

Two forms

Loop is a single node. You wire its onIterate exit to the first node of the body, and you wire the end of the body back to the Loop node yourself. That back edge is the iteration. The body is an ordinary run of nodes on the canvas, so nothing constrains where it goes, and a body with no path back to the Loop node runs once and stops on the first element.

Loop Group is a container. You drag the body inside the group's box, and membership rather than an edge is what returns control: when a node inside the group reaches an exit that is wired to nothing, the flow goes back to the group node and the next element starts. You do not draw a back edge at all.

Reach for Loop Group when the body is more than a couple of nodes, when you want it visually boxed, or when you need to leave the loop early (only the container form has a break exit). Reach for Loop when you would rather see the cycle drawn as an explicit edge, or when the body is a single node.

Exits

ExitLoopLoop GroupWhen it's taken
onIterateyesyesOnce per element, before the body runs.
onFinishedyesyesEvery element processed, or the array was empty.
breaknoyesA Loop Break node inside the group ran.
invalidInputyesyesThe Array field didn't resolve to an array, Maximum Iterations is below 1, the item variable can't be written, or Maximum Iterations was reached with items left.
invalidInputyesyesThe array holds more than 1000 elements. Taken on the first pass, before any element runs — see Limits and defaults.
invalidInputyesyesThe loop lost its iteration state mid-walk. The message names how many elements had been processed; the rest were not. See When a loop loses its place.

Fields

  • Array — the list to walk. A variable holding a JSON array, or a JSONPath that resolves to one, for example $(order.items). A value that isn't an array takes invalidInput. An empty array takes onFinished without running the body once.
  • Item Variable — the name the current element is written to before each pass.
  • Maximum Iterations — a runaway guard. Defaults to 100, and must be between 1 and 1000.

The item variables

Each pass writes three variables, all derived from the name you put in Item Variable. For invoice:

  • $(invoice) — the current element.
  • $(invoice_index) — its zero-based position in the array.
  • $(invoice_count) — the length of the whole array.

They are ordinary conversation variables in the same bag as everything else, not a reserved namespace. Two consequences worth knowing:

  • A loop nested inside another loop does not shadow its parent, as long as the two loops use different item variable names. Both sets stay readable in the inner body. Reusing one name overwrites the outer element on the inner loop's first pass, and the outer loop keeps running against the overwritten value. Validation rejects the reuse only when the outer loop is a Loop Group, because containment is the only nesting the editor can read: one Loop node driving another through ordinary edges gets no warning. Give every loop in a nest its own item variable name.
  • The variables are not cleared when the loop finishes. After onFinished, $(invoice) still holds the last element. Don't read them downstream of the loop expecting them to be empty.

Maximum Iterations is a fault, not a batch size

Reaching Maximum Iterations with items still unprocessed takes the invalidInput exit. It does not take onFinished.

That is deliberate: a loop that dropped most of its work should not report success and let the downstream branch act on it. The message names the loop, the limit, and how much was left, so you can tell "my cap is too small" from "my data is wrong":

Loop Group "processInvoices" stopped at its maximum of 100 iterations with 42 of 142 items unprocessed.

Wire invalidInput on the loop node if you want to handle that case. Left unwired inside a Loop Group it is claimed by the group as an ordinary end-of-iteration hop, which is not what you want for a fault.

The node-execution budget is the real ceiling

Maximum Iterations is usually not what stops a long loop. Every workflow run has a total node-execution budget, 500 by default, and each pass of a loop spends one execution per node in the body plus one for the loop node itself. So a loop with an n-node body completes at most about:

500 / (n + 1) iterations

A four-node body costs five executions per pass, so the default cap of 100 and the budget land in roughly the same place. A five-node body costs six per pass and gets about 83 iterations, well short of a cap of 100. A ten-node body gets about 45.

Treat the figure as an estimate, accurate only when the body runs every node on every pass. A body that branches runs fewer nodes per pass and gets further. Everything else on the run spends the same budget, so nodes before the loop, a nested loop, and the loop node's own final pass all mean fewer iterations than the division suggests.

When you verify or publish a Loop Group whose cap times its body size exceeds the budget, validation warns and gives you the number:

Loop Group 'processInvoices' may run 100 iterations over the 5 nodes inside it. That is
6 node executions per pass (the 5 inside plus the group itself) and 600 in all, against
the 500 one workflow run may execute by default. It stops after 83 iterations, on the
run's node-execution limit rather than on its own maximum. Lower 'maxIterations' to 83,
or move nodes out of the group.

The warning is only produced for the container form. A plain Loop node's body is author-drawn edges, so nothing can count it, but the budget applies to it just the same.

The budget is a per-organization limit and yours may have been raised or lowered; the warning always does its arithmetic against the default.

Leaving a loop early

Drop a Loop Break node inside a Loop Group and wire the body to it. Running it abandons the remaining elements and sends the flow out the group's break exit.

Loop Break has no fields and nothing to wire onward: the group decides where the flow goes next, so wire the group's break exit rather than an exit on the Loop Break node. Wiring the group's break exit is required even when the group holds no Loop Break today.

Loop Break walks past a Try / Catch on its way out, so a Loop Break inside a Try / Catch inside a Loop Group still leaves the loop.

The plain Loop node has no break, and there is no safe way to walk out of its body by an edge that stays inside whatever encloses the loop. Nothing discards the loop's iteration state on that route: for the rest of the run the per-node repeat guards stay suppressed on every node that follows, and re-entering the same Loop node later resumes the array where it left off instead of starting at the first element. The one exception is a plain Loop running inside a Loop Group, where an edge that leaves the group discards the group's iteration state and the plain loop's along with it.

Use a Loop Group with a Loop Break when you need to leave early. If you have to stay on the plain Loop node, wire the early-exit branch back to the Loop node and use a Condition in the body to skip the work for the elements you no longer care about: the loop still visits every one of them, and spends budget doing it, but every way it can end — running out of elements, or faulting on Maximum Iterations — discards its iteration state on the way out. The exception is the run's node-execution limit. A workflow killed there leaves the state behind until the run is torn down.

What validation checks

Verify and publish report these; saving a draft does not.

Errors:

  • A Loop whose onIterate exit has no path back to the Loop node, so it runs its body once and stops on the first element. Also an unwired onIterate.
  • A Loop Group with onIterate, onFinished, or break unwired. All three are required. onIterate must point at a node directly inside the group, not at one inside a nested group.
  • An empty Item Variable, or a name a workflow can't set: anything in the sys. namespace, a name containing a dot or a space, or a reserved root. Use letters, numbers, - and _.
  • An Item Variable that matches the one used by a Loop Group it sits inside.
  • A Maximum Iterations that is missing, or outside 1 to 1000.
  • A Loop Break that isn't inside a Loop Group. There's no loop for it to stop, and at runtime it takes invalidInput instead of breaking.
  • A Loop Break with any exit other than invalidInput or fallback wired.

Warnings:

  • A group with no nodes inside it.
  • A Loop Group whose onFinished or break exit points back inside the group. That node's unwired exit returns to the group, which reopens the frame it just popped and starts over from the first element.
  • The node-execution budget warning above.

A field holding a $(...) reference is left alone by the numeric and name checks, since the value isn't known until the node runs.

Limits and defaults

Value
Maximum Iterations default100
Maximum Iterations range1 to 1000
Longest array a loop can walk1000 elements
Node executions per workflow run500 by default, per organization
Group nesting depth5

An array longer than 1000 elements takes invalidInput on the first pass rather than processing the first 1000. Page the data in your integration or database query instead.

When a loop loses its place

A running loop keeps its position — the array, the current index, and the count — in short-lived storage, separate from the workflow itself. If that position is gone when the loop asks for the next element, the loop takes invalidInput with a message naming how far it got:

Loop Group "Send reminders" lost its iteration state after 12 of 40 items; the rest were not processed.

It faults instead of finishing on purpose. Taking onFinished would tell you the loop was done after 12 of 40, and everything wired downstream would act on that. Wire invalidInput somewhere that can tell the difference between a loop that finished and a loop that stopped, rather than joining it back into the onFinished path.

You are unlikely to see this on a workflow that runs straight through. The position is kept alive for as long as the workflow's own paused state whenever a node inside the body suspends — a Send SMS waiting for a reply, a Play Message waiting for digits, a Delay — so an ordinary wait inside the body is safe. It remains possible across an unusually long pause or if the position is evicted under storage pressure.

Next steps

On this page