Skip to content

Loops

Most flows are a fixed chain — do A, then B, then C. A Loop node breaks that: it repeats a subgraph once per element of a list, once per number in a range, or a fixed number of times. This is what turns "check out this one item" into "check out every item in the cart."

Set the loop's source in its inspector:

| Source | What it does | |---|---| | Expression | A {{...}} reference (or a literal JSON array) that must resolve to an array — e.g. {{cart.body.items}}, or a literal [1, 2, 3]. One iteration per element. | | Count | Runs a fixed number of iterations, 0 to count - 1. {{loopName.item}} is just the index as a string — use this when you don't need a real data element, just "do this N times." | | Range | An inclusive numeric sequence from a start to an end value, stepping by a configurable amount. Direction (counting up or down) is automatic — set start 10, end 1, and it counts down. |

If an expression source doesn't resolve, or resolves to something that isn't a JSON array, the loop node fails and its done branch (and everything past it) is skipped — same failure shape as any other node.

What runs each iteration — the loop body

Section titled “What runs each iteration — the loop body”

Connect the loop's body out-port to the node (or chain of nodes) you want repeated. Everything reachable from body is owned by the loop and runs once per iteration; everything connected to the loop's other out-port, done, runs exactly once, after every iteration finishes.

Each iteration gets a fresh scope — a request node inside the body sees its own iteration's values and does not leak into the next iteration.

A loop node named 'each' set to run three times, its body out-port wired to a fetch_item GET request; the run report below is expanded to show the loop ran 3 iterations, all passed, each taking about 40ms

Inside the body, reference the current iteration with:

  • {{loopName.item}} — the whole element (a JSON object becomes its string form; for Count/Range sources this is just the index).
  • {{loopName.item.path}} — a field of an object element, using the same dotted-path syntax as an extraction (e.g. {{loopName.item.id}}).
  • {{loopName.index}} — the 0-based iteration index.
  • {{loopName.count}} — the total number of iterations.

Downstream of done, reference the loop's overall results:

  • {{loopName.results}} — a JSON array with one entry per iteration.
  • {{loopName.passed}} / {{loopName.failed}} — counts.
  • continueOnError off (default) — the first failing iteration stops the loop; the loop node itself is marked failed, and done is skipped.
  • continueOnError on — every iteration runs regardless of failures; the loop node passes, and {{loopName.failed}} tells you how many didn't.

Every loop has a maxIterations cap (default 1000) that bounds how many elements it will ever process, even if the source array is longer or a Count/Range source would generate more. The excess isn't silently dropped — the run report shows the count was capped.

By default iterations run one at a time, in order. Raise Concurrency (up to 16) to run that many iterations in parallel — useful for exercising an endpoint under load or just finishing a long list faster. Regardless of concurrency, iterations always start in index order and their results are always reported in index order, so {{loopName.results}} and the run report stay predictable.

Iteration delay (ms) adds a pause between iteration starts — a wait between sequential iterations, or a stagger between parallel launches. It's never applied before the very first iteration.

  1. A Login request node extracts token.
  2. A Get Cart request node extracts items (body path data.items, an array of {id, qty} objects).
  3. A Loop node, source = Expression, {{getCart.outputs.items}}.
  4. The loop's body port connects to a Checkout Item request node, sending POST /checkout with body { "itemId": "{{cartLoop.item.id}}", "qty": "{{cartLoop.item.qty}}" } and an Authorization: Bearer {{login.outputs.token}} header. It asserts status equals 200.
  5. The loop's done port connects to a final Assert All Checked Out step, which could be a Condition node checking {{cartLoop.failed}} equals 0.

Run it, and the report expands the loop node into one row per cart item, each showing its own status and response.