loop
Iterate over an array field on each record and run a nested filter chain per item.
The loop filter walks an array field on each record and runs a nested
filter chain for every item. The current item is exposed under the
configured itemName alias; the root record stays available as
record. Use it to flatten records that carry an array of "cells",
"rows", or "lines" into top-level fields without dropping to a script
filter.
Minimal example
Extract a displayValue keyed by columnId into a flat
record.eventId:
filters:
- type: loop
field: cells
itemName: cell
filters:
- type: condition
expression: "cell.columnId == 8150579298996100"
then:
- type: mapping
mappings:
- source: cell.displayValue
target: record.eventIdOptions
| property | type | default | description |
|---|---|---|---|
typerequired | "loop" | — | Module type discriminator. Must be `loop` for this module. |
id | string | — | Unique identifier within the pipeline. |
name | string | — | Human-readable name. |
description | string | — | — |
enabled | boolean | true | Whether module is active. |
tags | array<string> | — | — |
onError | string | "fail" | Default error action. Case-insensitive; normalized to lowercase by the runtime. |
fieldrequired | string | — | Path to the array field, relative to the input record (root record for the outermost loop, parent scope for nested loops). Supports dot notation. |
itemNamerequired | string | — | Alias exposed to nested filters for the current item. Cannot be 'record', '_metadata', or 'loop', and must not duplicate an active parent loop alias. |
filtersrequired | array<object> | — | Nested filters executed for every item. |
Scope inside the loop
Nested filters operate on a scope — a synthetic record built once per item:
| Key | What it is |
|---|---|
record | The root record. Writes to record.* mutate the original record. |
<itemName> | The current item. Writes to <itemName>.* are persisted back into the array at the same position. |
| Parent aliases | Every active parent loop alias when loops are nested. |
_metadata | The root metadata. Loop iteration state lives at _metadata.loop.<itemName>.index (read-only). |
The scope is built fresh per item, but record and _metadata are
shared by reference — mutations are visible to subsequent items and
filters after the loop.
Loop metadata
The current index is exposed at _metadata.loop.<itemName>.index:
- type: loop
field: rows
itemName: row
filters:
- type: mapping
mappings:
- source: _metadata.loop.row.index
target: row.position_metadata.loop is read-only for nested filters. Any attempt to
write under that path is rejected with loop nested filters wrote to _metadata.loop which is read-only, including replacement or deletion
of _metadata itself.
Nested loops
Loops can be nested. Each inner loop adds its alias to the scope and keeps every parent alias available:
- type: loop
field: cells
itemName: cell
filters:
- type: loop
field: cell.children
itemName: x
filters:
- type: condition
expression: >-
_metadata.loop.cell.index == 2 &&
_metadata.loop.x.index == 0
then:
- type: mapping
mappings:
- source: x.label
target: record.firstConstraints on itemName:
- Cannot be
record,_metadata, orloop(reserved scope keys). - Cannot duplicate an active parent loop alias.
Item removal and expansion
| Nested filters return … | Effect on the array |
|---|---|
| Zero records for an item | Item is removed from the array. |
| Exactly one record | Item is updated with the result. |
| More than one record | Pipeline fails — item expansion is out of scope in v1. |
Use a nested condition + drop to remove items conditionally:
- type: loop
field: items
itemName: item
filters:
- type: condition
expression: "item.keep == false"
then:
- type: dropNon-object items
Scalar / array / null items pass through unchanged when nested filters
don't touch the alias. Sub-path writes such as item.foo = ... on a
non-object item are rejected to prevent silent map auto-creation by
the path engine.
Examples
name: loop-cells-extraction
version: 1.0.0
description: Iterate over a cells[] array and extract values into flat record fields by columnId.
tags:
- loop
- smartsheet
input:
type: httpPolling
schedule: "*/15 * * * *"
endpoint: https://source.example.com/api/rows
dataField: rows
filters:
- type: loop
field: cells
itemName: cell
filters:
- type: condition
expression: cell.columnId == 1
then:
- type: mapping
mappings:
- source: cell.displayValue
target: record.eventId
- type: condition
expression: cell.columnId == 2
then:
- type: mapping
mappings:
- source: cell.displayValue
target: record.coordinates
- type: condition
expression: cell.columnId == 3
then:
- type: mapping
mappings:
- source: cell.displayValue
target: record.customerName
- type: remove
target:
- cells
output:
type: httpRequest
endpoint: https://destination.example.com/api/events
method: POST
requestMode: single