CollectionOperation NodeTypeCode: collection-operation
Apply functional array operations — filter, map, reduce, sort, distinct, count, first, last, reverse — to any collection in the workflow without writing custom code. Per-item errors are tolerated and reported, not fatal by default.
The Standard Node Data Envelope
Every node's input and output — CollectionOperation included — travels inside the same standard envelope. Run metadata sits at the top level; the real payload lives under items, each entry wrapping its record in "json":
{
"timestamp": "2026-07-24T01:02:27.8652562Z",
"triggerTime": "2026-07-24T01:02:27.8652562Z",
"resource": "trigger",
"operation": "manual-trigger",
"status": "success",
"portName": "main",
"items": [
{
"json": {
"timestamp": "2026-07-24T01:02:27.8652562Z",
"triggerTime": "2026-07-24T01:02:27.8652562Z",
"resource": "trigger",
"operation": "manual-trigger",
"status": "success",
"portName": "main"
}
}
],
"success": true
}
CollectionOperation resolves the collection to operate on from InputData["items"] by default, unwrapping each entry's "json" value into one element — the same shared ResolveSourceRecords/ExtractRecordsFromItems helper DataMapping uses.
When you type a JSON array directly into inlineDataSource to test an operation, you write a plain array — [{...}, {...}] — not {"items":[{"json":{...}}]}. The node parses whatever you typed and normalizes it into the standard items/json shape before producing output. The envelope is the wire format between nodes; it is not what you author by hand.
When to Use
- Filter orders by status: An upstream node returns all orders for a customer. Use
filterwithitem.status === "active"to keep only the orders that need processing — no CodeExecute required. - Map records to extract just IDs: A database query returns full order objects. Use
mapwith expressionitem.orderIdto produce a clean array of order IDs for a bulk-cancel API call. - Sum or fold a collection to one value: Use
reducewithacc + item.totalandinitialValue: 0to total up line-item amounts. - Sort records by a field: A queue of pending approvals needs to be processed oldest-first. Use
sortwithfield: "createdAt"anddirection: "asc". - Get distinct values of a field: An event log contains multiple entries per region. Use
distinctwithfield: "region"to get a unique list of affected regions for targeted notifications. - Tolerate bad records instead of failing the whole batch: A collection with a handful of malformed items (a missing field, an unparsable value) shouldn't abort processing of the rest. The default
onError: "continue"behavior skips only the failing items and reports them inerrorRecords/errorCount— the node still succeeds.
Configuration
| Setting | Required | Description |
|---|---|---|
operation |
Required | The operation to perform on the collection. One of: filter, map, reduce, sort, distinct, count, first, last, reverse. |
inlineDataSource |
Optional | An inline JSON array literal. Shared base-class setting (also used by DataMapping) — not unique to this node. If non-empty, it wins outright over reading InputData["items"]. |
expression |
Optional* | A JavaScript expression evaluated per item (for filter, map) or per accumulation step (for reduce). The current element is item; reduce additionally exposes acc. *Required for filter, map, and reduce — validation fails without it. |
field |
Optional | Field name used by sort (the field to sort by) and distinct (the field to deduplicate on). Omit for arrays of primitives. |
direction |
Optional | Sort direction for the sort operation. "asc" (default) or "desc". |
initialValue |
Optional | The starting accumulator value for the reduce operation, given as a plain string that's parsed into a number, boolean, or left as a string (e.g. "0" for sum, "" for concatenation). Defaults to null if omitted. |
onError |
Optional | Shared base-class setting (also on DataMapping). "continue" (default) skips failing items and never fails the node on error count alone. "error" fails the node once errorCount exceeds maxErrorCount. See Error Handling below. |
maxErrorCount |
Optional | Default 0. Only consulted when onError is "error": the node fails once errorCount exceeds this value. 0 means "fail on any error." |
maxErrorsToCollect |
Optional | Default 0 (unlimited). Safety cap: once this many item errors have occurred, the operation stops processing further items and the node fails — regardless of onError. Protects against a systemically-broken expression burning through a huge collection before anyone notices. |
Operations Reference
| Operation | Expression Variables | Result Shape | Description |
|---|---|---|---|
filter | item — current element | List (subset), via items | Evaluates the expression for each element. Keeps only elements where the expression is truthy. item.status === "active" keeps only active items. |
map | item — current element | List (transformed), via items | Transforms each element by evaluating the expression. item.id returns an array of ID strings. { id: item.id, label: item.name } returns reshaped objects. A result of NaN (e.g. arithmetic on a missing/non-numeric field) is treated as a per-item error, not silently written through. |
reduce | acc — accumulator, item — current element | Scalar or object, via a single output item | Folds the collection to a single value. acc + item.total with initialValue: "0" produces a sum. A NaN result at any step is a per-item error (that step is skipped; acc keeps its prior value). |
sort | n/a — uses field and direction | List (sorted), via items | Sorts by the specified field in ascending or descending order. Uses natural comparison for strings and numeric comparison for numbers. |
distinct | n/a — uses field | List (deduplicated), via items | Returns unique elements, keeping the first occurrence. For object arrays, specify field to deduplicate by field value. For primitive arrays, omit field. |
count | n/a | Integer, via a single output item | Returns the total number of elements in the resolved collection. |
first | n/a | Element or null, via a single output item | Returns the first element, or null if the collection is empty. |
last | n/a | Element or null, via a single output item | Returns the last element, or null if the collection is empty. |
reverse | n/a | List (reversed), via items | Returns the collection in reverse order. Does not sort — simply reverses the existing element order. |
Error Handling: Per-Item Failures Don't Abort the Batch
filter, map, and reduce evaluate a JS expression once per item. A single bad item — a thrown expression error, or a NaN arithmetic result — never aborts the whole operation. It's recorded as a failure in errorRecords/errorCount, and processing continues with the next item.
| Setting | Effect |
|---|---|
onError: "continue" (default) | Failing items are skipped and counted. The node always succeeds regardless of how many items failed (unless maxErrorsToCollect triggers a circuit-break — see below). |
onError: "error" | The node fails (routes to error) once errorCount exceeds maxErrorCount. With the default maxErrorCount: 0, that means any single item failure fails the node. |
maxErrorsToCollect > 0 | A safety cap that applies regardless of onError: once this many failures have occurred, the operation stops processing further items immediately and the node fails, reporting "...stopped after N item error(s) reached maxErrorsToCollect". |
Even in onError: "continue" mode, the failed items are not silently dropped from the record — each one appears in errorRecords as {"item": <original item>, "error": "<message>"}, capped at maxErrorsToCollect entries (or unlimited if it's 0) even though errorCount keeps counting every failure beyond that cap.
Output Ports
| Port | When It Fires |
|---|---|
success | The operation completed. This includes the case where some items failed under onError: "continue" — check errorCount to detect partial failures even on the success port. |
error | The collection could not be resolved at all (no inlineDataSource and no upstream items), the operation name is missing/invalid, the operation-specific expression is missing, or the error-handling thresholds above (onError: "error" + maxErrorCount, or maxErrorsToCollect) were exceeded. |
Output Fields
| Field | Type | Description |
|---|---|---|
status | string | Literal "success" on the success path. |
successCount | integer | Number of items that processed without error. |
errorCount | integer | Total number of items that failed, including any beyond the maxErrorsToCollect cap. |
errorRecords | array | Up to maxErrorsToCollect failed items, each as {"item": <original>, "error": "<message>"}. |
count | integer | Only present for list-shaped operations (filter/map/sort/distinct/reverse) — the number of successful result records, i.e. successCount. |
items | array | Standard [{ "json": <record> }, ...] shape. For list operations, one item per successful result record. For scalar operations (reduce/count/first/last), a single item wrapping the scalar or element value. |
Sample Configurations
Filter — only active orders, tolerate bad records
{
"nodeType": "collection-operation",
"settings": {
"operation": "filter",
"expression": "item.status === \"active\"",
"onError": "continue",
"maxErrorsToCollect": 10
}
}
Map — extract order IDs only
{
"nodeType": "collection-operation",
"settings": {
"operation": "map",
"expression": "item.orderId"
}
}
Reduce — sum all line item amounts
{
"nodeType": "collection-operation",
"settings": {
"operation": "reduce",
"expression": "acc + item.amount",
"initialValue": "0"
}
}
Sort — newest records first
{
"nodeType": "collection-operation",
"settings": {
"operation": "sort",
"field": "createdAt",
"direction": "desc"
}
}
Sample Output — filter with partial errors
{
"status": "success",
"successCount": 2,
"errorCount": 1,
"errorRecords": [
{ "item": { "id": "item_003", "status": null }, "error": "Cannot read properties of null (reading 'toLowerCase')" }
],
"count": 2,
"items": [
{ "json": { "id": "item_001", "status": "active" } },
{ "json": { "id": "item_002", "status": "active" } }
]
}
Expression Reference
| Expression | Value |
|---|---|
{{ $output.collectionOp.items[0].json }} | The first result record (or the scalar/element for reduce/count/first/last). |
{{ $output.collectionOp.successCount }} | How many items processed without error — useful for downstream IfCondition branching. |
{{ $output.collectionOp.errorCount }} | How many items failed. Check this even on the success port to detect partial failures under onError: "continue". |
Node Policies & GuardRails
| Policy | Rationale |
|---|---|
Check errorCount even on the success port | Under the default onError: "continue", the node still reports success even when some items failed — the failures are visible only in errorCount/errorRecords, not by port routing. |
Use item (and acc for reduce) — not the source field name — in expressions | Every element in the collection is exposed as item regardless of the field names in the source object. Write item.status, not order.status. |
Set onError: "error" only when any single bad item should hard-fail the workflow | The default "continue" is almost always right for exploratory/bulk operations. Reserve "error" + a tuned maxErrorCount for pipelines where partial success is unacceptable. |
Set a maxErrorsToCollect cap on large or untrusted collections | Without a cap, a systemically-broken expression (e.g. referencing a field that doesn't exist on any item) still runs to completion before you find out — every item fails, one at a time. A cap stops the operation early. |
Always provide an explicit initialValue for reduce | Without one, the accumulator starts as null, which can produce unexpected results (e.g. null + item.total evaluating to NaN on the first item, recorded as an error) rather than the numeric/string seed you intended. |
| For complex multi-step transformations, chain multiple CollectionOperation nodes or use CodeExecute | CollectionOperation is designed for single-operation array manipulation. When you need to chain multiple operations (filter then map then sort), either use multiple nodes in sequence or a single CodeExecute node. |
Pattern Examples
See Examples for elaborate, realistic worked examples including error-handling behavior and inlineDataSource usage.
Pattern 1 — Filter Active Subscriptions, Then Extract Emails for Bulk Send
// Node 1: CollectionOperation — filter active premium subscriptions
{ "operation": "filter", "expression": "item.status === \"active\" && item.tier === \"premium\"" }
// items: premium active subscription records only
// Node 2: CollectionOperation — extract customer email addresses
{ "operation": "map", "expression": "item.customerEmail" }
// items: one item per email string, ready for bulk email send
Pattern 2 — Sum Invoice Totals and Get Distinct Regions
// Node 1: Sum all invoice amounts
{ "operation": "reduce", "expression": "acc + item.totalAmount", "initialValue": "0" }
// Node 2: Get distinct billing regions from the same invoices
{ "operation": "distinct", "field": "billingRegion" }
// items: ["EMEA", "APAC", "AMER"] (unique regions only), one item per region