Elizon Docs
Workflows

Loop

Run part of the workflow once per item of a list, one item at a time or several at once.

The Loop node (loop) takes a list and runs the nodes on its loop handle — the loop body — once per item, one after another or several items at the same time (batchSize). When every item is done, the run continues from its done handle.

Configuration

FieldTypeDefaultDescription
sourceArraystring''A {{ }} expression that resolves to a list, for example {{ trigger.body.orders }} or {{ upstream.search.output.rows }}.
batchSizeinteger ≥ 11How many items run at the same time (1 = one after another). Each iteration still gets one item.

Inside the loop body

ReferenceValue
{{ loop.item }}The current item, whatever batchSize is.
{{ loop.item.<field> }}A field of the current item.
{{ loop.index }}The current iteration, starting at 0.

In a loop nested inside another loop, loop.* refers to the innermost one. Writes to a variable inside the body last only for that iteration.

Output

Read a body node's results from after the loop: {{ upstream.<body node id>.output.iterations }} is a list of that node's output, one entry per iteration, in order. That is almost always what you want. After the loop, a body node's output is { iterations, loopNodeId }, not the list itself, so {{ upstream.<body node id>.output.0 }} does not exist; use output.iterations.0. An iteration in which the node did not run (its branch was not taken) has no entry, so the list can be shorter than the number of items.

The Loop node's own output is the per-iteration record the run trace shows: a list with one entry per iteration, each an object keyed by body node id ({{ upstream.<loop node id>.output.0.<body node id> }}). A Loop with nothing on its loop handle records { index, item } for each item.

Limits and errors

A Loop node stops after a bounded number of iterations — 100 by default on a self-hosted deployment, configurable via the WORKFLOW_LOOP_ITERATION_CEILING environment variable up to a hard cap of 1000.

Hitting that bound is not a failure. The run ends with status loop_limit_reached, keeps whatever the loop did process, and reports no error — the bound is a declared safety limit, and it held. Treat it as its own outcome in your integration rather than folding it into either completed or failed.

  • A Wait or Human Review node cannot go inside a loop body; it fails the loop. Put it before or after the loop (a batch Human Review reviews a whole list in one request).
  • A sourceArray that does not resolve to a list fails the node.
  • With batchSize above 1, up to that many iterations run at the same time, and the next group starts when the whole group has finished. Parallel iterations share nothing: each has its own {{ loop.item }}, {{ loop.index }} and variable writes, and results are still listed in the source order. Don't rely on one iteration seeing another's effects (a row it wrote, an email it sent); use batchSize: 1 when order matters.

Example

Summarise each attachment of an email, then send one message with every summary:

workflows/summarise-attachments.yaml
name: Summarise attachments
description: Summarises each attachment of an incoming email.
graph:
  nodes:
    - id: mail
      type: trigger_email
      position: { x: 0, y: 0 }
      data:
        downloadAttachments: true
        pollMinutes: 5
    - id: each-attachment
      type: loop
      position: { x: 280, y: 0 }
      data:
        sourceArray: '{{ trigger.attachments }}'
        batchSize: 1
    - id: summarise
      type: ai_prompt
      position: { x: 560, y: -80 }
      data:
        llm_slot: summary-model
        model_id: gpt-4o-mini
        instructions: Summarise this document in three sentences.
        inputExpression: '{{ loop.item.text }}'
        outputFormat: text
    - id: collect
      type: edit_fields
      position: { x: 560, y: 80 }
      data:
        rows:
          - { id: summaries, name: summaries, value: '{{ upstream.summarise.output.iterations }}' }
        keepOnlySetFields: true
  edges:
    - { id: mail-to-loop, source: mail, target: each-attachment }
    - { id: loop-body, source: each-attachment, target: summarise, sourceHandle: loop }
    - { id: loop-done, source: each-attachment, target: collect, sourceHandle: done }

In a Package

  • Travels: sourceArray, batchSize
  • Converted: none
  • Never carried: none