Node Compatibility with the Iterator
This guide explains which nodes can be used inside the Iterator → [processing] → MergeResults pattern, and which cause errors or unexpected behavior, with the exact technical reason for each case.
How the iterator works (technical summary)
Section titled “How the iterator works (technical summary)”When the Iterator node processes an array of N items, the engine generates N independent parallel executions (limited by maxConcurrent, default 5). Each execution:
- Receives only its item in
data. - Gets its own metadata in
_meta_(currentIndex,totalChunks,workflowRunId). - Runs isolated through the following nodes.
- When it reaches a merge node, it deposits its result and waits for the others.
The problem arises when a node maintains global state (writes to the DB with a fixed key, uses the same ID for all executions, or depends on shared resources): two concurrent items collide on the same resource.
Available merge types
Section titled “Available merge types”| Node | Storage | When to use |
|---|---|---|
| mergeResults | In memory | The main option for Iterator. Fast, no DB hits. |
| mergeMultiple | Database | For convergences from multiple unrelated branches. Not for Iterator. |
| merge | Database | Legacy, doesn’t support nested iterations. Avoid with Iterator. |
| mergeIterator | Pass-through | Only reformats; doesn’t wait for chunks. Very specific cases. |
General rule: Iterator → [nodes] → mergeResults.
Compatible nodes ✅
Section titled “Compatible nodes ✅”These nodes work correctly inside an iteration because they are stateless: each execution operates on its own data without touching shared resources.
Transformation and logic
Section titled “Transformation and logic”| Node | Notes |
|---|---|
evaluateExpression | Pure JavaScript logic on data. No side effects. |
condition / decision | Logical branching. No DB writes. |
switch / router / multiroute | Routing. No state. |
editFields | Renames and transforms fields in memory. |
dataTransform / dataMapper / dataMapping | Data transformations. No state. |
keyFilter / objectToArray / arrayFilter | Object/array operations. |
jsonToXml / xmlToJson / jsonToToon / toonToJson | Format conversions. |
splitStringList / schemaParser | Data transformation. No state. |
htmlTemplate | Renders template with the item’s variables. Each item generates its own HTML. |
barcodeGenerator | Generates PNG image in memory. No global state. |
HTTP integration and APIs
Section titled “HTTP integration and APIs”| Node | Notes |
|---|---|
http | Each item makes its own independent HTTP call. |
graphql / graphqlQuery | Same principle as HTTP. |
executeQuery (SELECT) | MySQL read. No collision if there’s no concurrent write on the same row. |
sqlQuery / sqlQueryPostgres / sqlQueryMSSQL / sqlQueryOracle | Safe reads; safe INSERT writes if there’s a unique key per item. See notes. |
Communications (1 message per item)
Section titled “Communications (1 message per item)”| Node | Notes |
|---|---|
sendMail | Each item sends its own email. No collision. |
slack / discord / telegram / whatsapp | Each item sends its own message. |
ntfyNotify | Notification per item. No state. |
Storage (unit write operations)
Section titled “Storage (unit write operations)”| Node | Notes |
|---|---|
dataStore (action add) | Each item inserts 1 record. No collision between items. |
awsS3 / s3Compatible (upload) | Each item uploads its own file. No collision if the key is unique per item. |
filetransfers (upload) | Same principle. |
save2json / saveToExcel | If the path includes {{_meta_.currentIndex}} to avoid overwriting. |
getVariable | Read-only. No risk. |
Unit-write integrations
Section titled “Unit-write integrations”| Node | Notes |
|---|---|
| Shopify (create order, update product) | Each item operates on its own resource. |
| Airtable (insert) | Each item inserts 1 row. |
| Google Sheets (append) | Each item appends 1 row. No collision. |
| Mailchimp (subscribe contact) | Individual operation per item. |
Any Create* integration node | No shared state. |
Incompatible nodes ❌
Section titled “Incompatible nodes ❌”These nodes cause errors or corrupted data when executed inside an iteration.
delay — Unique key violation
Section titled “delay — Unique key violation”Technical problem: The delay node records the deferral in the workflow_delays table with this unique key:
UNIQUE KEY unique_workflow_delay (workflow_id, delay_node_id)The delay_node_id is the node’s ID in the canvas, which is the same for all items. When 5 concurrent items try to insert into workflow_delays with the same (workflow_id, delay_node_id), the second item fails with Duplicate entry ... for key 'unique_workflow_delay'.
Symptom: Only the first item to reach the Delay node is processed; the rest fail with a SQL error.
Solution: Place the Delay before the Iterator or after the Merge, never inside.
✅ Delay → Iterator → [process] → mergeResults✅ Iterator → [process] → mergeResults → Delay❌ Iterator → Delay → [process] → mergeResultssubWorkflow — parent_instance_id collision
Section titled “subWorkflow — parent_instance_id collision”Technical problem: When launching a sub-workflow, the node registers:
parent_workflow_id: config.workflowId, // Same for all itemsparent_instance_id: config.workflowId, // Same for all itemsIf 5 concurrent items execute the same subWorkflow, all 5 children share the same parent_workflow_id. Child instances overwrite each other in the workflowinstances table; the parent loses track of earlier children and the results of first items are lost.
Symptom: The execution history shows fewer child instances than expected. The first completed items don’t appear in the parent’s results.
Solution: Don’t use subWorkflow inside iterations. If you need to execute multiple child workflows in parallel, design the logic as a single workflow that receives an array and uses its own Iterator internally.
Triggers (Webhook, FormTrigger, DataStoreTrigger, MqttTrigger, RedisTrigger, AgentFollowUpTrigger)
Section titled “Triggers (Webhook, FormTrigger, DataStoreTrigger, MqttTrigger, RedisTrigger, AgentFollowUpTrigger)”Problem: Triggers are workflow start nodes, not intermediate processors. They wait for external signals (HTTP, events, forms) and cannot act as transformers inside an iteration chain.
Symptom: Format errors or the node stays waiting for an external event that never arrives.
Solution: Triggers only go at the beginning of the workflow. Never inside an iterator.
start / end
Section titled “start / end”Problem: These are special engine nodes. start is the workflow entry point; end terminates the entire execution. Placing them inside an iterator breaks the batch logic.
Solution: Do not use them inside iterators.
Gray zone nodes ⚠️
Section titled “Gray zone nodes ⚠️”These work in certain cases but require special attention.
setVariable — Overwrite between concurrent items
Section titled “setVariable — Overwrite between concurrent items”Mechanism: Writes to the memorystorage table with (client_id, key_name).
Risk: If 5 concurrent items execute setVariable('result', data), they all write to the same key. The last item to write wins; previous ones are silently overwritten.
Item 1: setVariable('result', {id:1}) → DB: result={id:1}Item 2: setVariable('result', {id:2}) → DB: result={id:2} ← overwrites 1Item 3: setVariable('result', {id:3}) → DB: result={id:3} ← overwrites 2When it works without issue: When each item writes to a unique key per index:
setVariable('result_{{_meta_.currentIndex}}', data)→ result_0, result_1, result_2 → no collisionWhen it fails: When all items write to the same key (to accumulate a counter, for example). For that, use the summary array returned by mergeResults and process it after the merge.
dataStore (actions filter, update, delete)
Section titled “dataStore (actions filter, update, delete)”| Action | Compatibility | Detail |
|---|---|---|
add | ✅ | Inserts 1 row per item. No collision. |
filter | ⚠️ | Multiple concurrent reads are safe. If another item modifies the same record between read and use → inconsistency. |
update | ⚠️ | Race condition risk if two items update the same record. Only safe if each item operates on a different record. |
delete | ⚠️ | Same as update. If two items try to delete the same record, the second fails silently. |
bulk | ✅ | Atomic operation on a batch. No collision. |
Recommendation: Inside the iterator, preferably use add. The update and delete operations are only safe if each item is guaranteed to operate on a different record (e.g., using a unique field like order_id).
executeQuery / sqlQuery (INSERT, UPDATE, DELETE)
Section titled “executeQuery / sqlQuery (INSERT, UPDATE, DELETE)”INSERT with unique key: Safe if the unique key includes a field that varies per item (e.g., order_id). If the key doesn’t vary, the second item duplicates and fails.
UPDATE: Safe if each item updates its own row. Risk if two items compete for the same row.
DELETE: Same as UPDATE.
Recommendation: Make sure the query includes an item identifier (WHERE id = {{order_id}}) to guarantee each item operates on its own record.
Agent / agentChat — Shared conversation history
Section titled “Agent / agentChat — Shared conversation history”Problem: The agent maintains conversation history indexed by agent_id, which is the same for all items. When 5 concurrent items use the same agent:
- The history gets mixed: agent responses blend context from different items.
- The agent “remembers” messages from other items as if they were from the same thread.
When it works: If each item configures a unique session_id or agent_id (e.g., including _meta_.currentIndex or a business ID from the item).
When it fails: With a fixed agent_id shared across all items.
repeater inside iterator — Nested iteration
Section titled “repeater inside iterator — Nested iteration”Technically works: the engine assigns mergeLevel and mergeGroupKey to each nesting level, and mergeResults distinguishes them correctly.
Overhead: N items from the iterator × M repetitions from the repeater = N×M concurrent executions. This can saturate the concurrency pool and consume more memory.
Recommendation: Use it only when the logic explicitly requires it. In many cases the input array can be restructured to avoid nesting.
generatepdf / ocrExtract / pdfExtractor — Memory load
Section titled “generatepdf / ocrExtract / pdfExtractor — Memory load”Each item processes a full file in memory. With maxConcurrent=5 and files of several MB, hundreds of MB can be consumed simultaneously.
Solution: Reduce maxConcurrent in the Iterator when nodes process large files. A value of 2-3 usually works well.
Visual summary
Section titled “Visual summary”✅ COMPATIBLE ⚠️ WITH CONDITIONS ❌ INCOMPATIBLE| Node | Status | Short reason |
|---|---|---|
evaluateExpression | ✅ | No state |
condition / decision | ✅ | No state |
http / graphql | ✅ | Each item → its own call |
sendMail / slack / telegram | ✅ | 1 message per item |
dataStore add | ✅ | Inserts 1 row per item |
getVariable | ✅ | Read-only |
htmlTemplate / barcodeGenerator | ✅ | In-memory processing |
awsS3 upload | ✅ | If the key is unique per item |
setVariable | ⚠️ | Safe only with unique key per index |
dataStore filter/update/delete | ⚠️ | Safe if each item uses its own record |
executeQuery INSERT/UPDATE | ⚠️ | Safe if WHERE guarantees isolation |
Agent / agentChat | ⚠️ | Requires unique session_id per item |
repeater (nested) | ⚠️ | Works, but with N×M overhead |
generatepdf / ocrExtract | ⚠️ | Reduce maxConcurrent with large files |
delay | ❌ | Unique key violation in DB |
subWorkflow | ❌ | parent_instance_id collision |
Webhook / FormTrigger | ❌ | Start triggers, not processors |
DataStoreTrigger / MqttTrigger | ❌ | Same |
start / end | ❌ | Special engine nodes |
Recommended patterns
Section titled “Recommended patterns”Basic pattern (most common)
Section titled “Basic pattern (most common)”[Source] → Iterator → [HTTP / evaluateExpression / dataStore.add] → mergeResults → [Next node]Process list with individual actions and consolidate
Section titled “Process list with individual actions and consolidate”[Get list] → Iterator → executeQuery → sendMail → mergeResults → [Final summary]Generate documents per item
Section titled “Generate documents per item”[Get list] → Iterator → barcodeGenerator → htmlTemplate → generatepdf → mergeResults → [Send]Using setVariable safely in an iteration
Section titled “Using setVariable safely in an iteration”// In the setVariable config inside the iterator:keyName: "result_{{_meta_.currentIndex}}"// → generates result_0, result_1, result_2 ... no collisionWhere to place the Delay
Section titled “Where to place the Delay”✅ [Start] → Delay → Iterator → [process] → mergeResults → [End]✅ [Start] → Iterator → [process] → mergeResults → Delay → [End]❌ [Start] → Iterator → Delay → [process] → mergeResults ← FailsRelated nodes
Section titled “Related nodes”- Iterator — splits an array into parallel items
- MergeResults — recombines results in memory
- MergeMultiple — convergence from multiple branches (don’t use with Iterator)
- Repeater — generates N repetitions of the same payload
- Delay — defers execution (use before or after the Iterator)
- Good Practices — general workflow design guide