Portal Community

When to Use

Configuration

FieldRequiredDescription
Items Required Variable name or template expression that resolves to the array to iterate over. This can be a simple variable name (e.g. recipients, resolved as $var.recipients) or a full template expression (e.g. {{ $output.findDocs.documents }}). The value must resolve to a JSON array — a scalar or object will cause a validation error.
Empty arrays are handled gracefully: If the resolved array is empty, the body port never fires. Execution proceeds directly to the done port without error. This means you do not need an IfCondition to guard against empty collections before a Loop node.

Output Ports

PortFires When
bodyOnce per iteration — fires for each element in the array in order. current_item and current_index are injected into ExecutionMemory before this port fires. This is where you connect the per-item processing nodes.
doneAfter all iterations have completed normally, or immediately after a Break node signals an early exit. Connect post-loop logic here — aggregation, summary notification, or continuation of the workflow.
errorIf the Items expression fails to resolve, or if the resolved value is not an array. This port does not fire for errors inside the loop body — those propagate through the body subgraph's own error handling.

OutputData Fields

Fields available after the loop via $output.<nodeKey>:

FieldTypeDescription
collection_sizeintTotal number of elements in the array. Available immediately after the loop node (including during the done port path).
current_indexintThe index of the most recently processed item (0-based). On the done port, this equals collection_size - 1 for a completed loop, or the index at which a Break fired.
itemsarrayThe full resolved array that was iterated. Useful for downstream nodes that need the original collection without re-fetching it.

Per-Iteration Variables (Injected into ExecutionMemory)

These variables are available inside the loop body on every iteration via $var:

VariableTypeDescription
current_itemobjectThe current array element. For an array of objects, access fields directly: {{ $var.current_item.orderId }}.
current_indexintThe 0-based index of the current iteration. First item is 0, last is collection_size - 1.

Sample Configuration

Loop over a variable

{
  "Items": "recipients"
}

Loop over a node output (MongoDB documents)

{
  "Items": "{{ $output.findOrders.documents }}"
}

Loop over a JSON array field from the trigger

{
  "Items": "{{ $json.lineItems }}"
}

Sample Output

Per-Iteration Output (Success Port)

The loop fires the success port once per item. Each execution carries the current item's data plus loop metadata.

{
  "productId": "PRD-00451",
  "sku": "WGT-BLK-L",
  "name": "Widget Black Large",
  "stock": 142,
  "reorderPoint": 50,
  "supplierId": "sup_001",
  "_loop": {
    "currentIndex": 3,
    "totalItems": 47,
    "isFirst": false,
    "isLast": false
  }
}

Loop Completed Port

{
  "_loop": {
    "completed": true,
    "totalItems": 47,
    "processedCount": 47,
    "errorCount": 0,
    "durationMs": 2341
  }
}

Expression Reference

ExpressionReturns
{{ $var.current_item }}Current element — available inside loop body only.
{{ $var.current_item.fieldName }}A specific field of the current object element.
{{ $var.current_index }}0-based integer index of the current iteration.
{{ $output.myLoop.collection_size }}Total number of items in the array (available on done port).
{{ $output.myLoop.items }}The original full array (available on done port).

Validation Errors

ErrorCause
VAL_MISSING_ITEMSThe Items field is empty or whitespace.
VAL_ITEMS_NOT_ARRAYThe resolved Items expression returned a non-array value (scalar, object, or null).

Node Policies and GuardRails

Scope of current_item: $var.current_item and $var.current_index are only valid inside the loop body (the subgraph connected to the body port). They are not available on the done port path. On the done port, use $output.loopNodeKey.items to access the full collection.

Pattern Examples

Pattern 1 — Batch Email Dispatch

Load a recipient list from a database query, then loop to send a personalised email to each recipient. After all emails are sent, trigger a completion notification.

MongoDB  [key: "getRecipients"]
  (find all active subscribers)
  └─► Loop  [key: "sendEmails"]
        Items: {{ $output.getRecipients.documents }}
        ├─► body ──► EmailSmtp  [key: "sendOne"]
        │             To:      {{ $var.current_item.email }}
        │             Subject: "Your monthly report is ready"
        │             Body:    "Hi {{ $var.current_item.firstName }}, ..."
        └─► done ──► SlackNode  (notify team: sent {{ $output.sendEmails.collection_size }} emails)

Pattern 2 — Per-Item Validation with Early Exit

Validate each item in a cart. If any item fails stock check, Break exits immediately and routes to an error response. Otherwise the done port proceeds to checkout.

FormTrigger  (checkout submitted)
  └─► Loop  [key: "validateCart"]
        Items: {{ $json.cartItems }}
        ├─► body ──► HttpRequest  [key: "checkStock"]
        │             GET /inventory/{{ $var.current_item.productId }}
        │             └─► IfCondition
        │                   Condition: {{ $output.checkStock.quantity > 0 }}
        │                   ├─► true  ──► (continue to next iteration)
        │                   └─► false ──► Break  (exit loop immediately)
        └─► done ──► [proceed to payment processing]

Pattern 3 — Accumulating Results Across Iterations

Transform each item and accumulate results into a list variable, then use the full list after the loop completes.

VariableAssignment  [key: "initResults"]
  processedOrders = []
  └─► Loop  [key: "processOrders"]
        Items: {{ $json.orders }}
        ├─► body ──► Function  [key: "transformItem"]
        │             (compute enriched order object)
        │             └─► VariableAssignment
        │                   processedOrders = {{ $var.processedOrders }}
        │                                     .concat([$output.transformItem.result])
        └─► done ──► HttpRequest  (POST /api/orders/bulk)
                       Body: {{ $var.processedOrders }}