Merge
Combine outputs from incoming workflow branches into one payload.
The Merge node combines the outputs from incoming workflow branches and passes one merged payload downstream. It is most useful with two or more branches; a single incoming branch is allowed, but the output still uses the selected Merge mode and the { success, mode, inputCount, result, meta } envelope. Use it when parallel nodes produce related data and the next node needs to read everything from one place.
Common uses:
- Keep multiple API responses together without overwriting fields
- Combine several lists into one list
- Pair list items by position
- Join list items by a shared field such as
address,mint, orsymbol
Merge is edge-driven: it reads the nodes connected directly into it. You do not write template expressions in the Merge node itself.
Configuration
| Field | Type | Required | Description |
|---|---|---|---|
| Node Label | string | No | Display name shown on the canvas |
| Response Name | string | Recommended | Variable name used by downstream nodes. Defaults to mergeResponse |
| Mode | select | Recommended | How incoming branches are combined. Defaults to Keep each branch separate if unset |
| Match Field | string | Only for Match list items by field | Field path used to join list items, for example address or data.mint |
| Per-Item Combine | select | For list matching modes | Whether matched items are flattened together or kept under each branch name |
Downstream nodes read the merged value under .result:
{mergeResponse.result}If you change Response Name to mergedPrices, downstream nodes use:
{mergedPrices.result}Modes
| Mode in the editor | Best for | What it does |
|---|---|---|
| Keep each branch separate | Safest default; multiple API responses | Stores each branch under its response name |
| Combine matching fields | Object outputs with different field names | Spreads all incoming object fields into one object. Later branches overwrite earlier branches on key conflicts |
| Stack lists together | Multiple arrays | Concatenates arrays end-to-end |
| Match list items by position | Arrays already in the same order | Combines item 1 with item 1, item 2 with item 2, and so on. Output length is the shortest input list |
| Match list items by field | Arrays that share an ID field | Inner-joins items where every branch has the same value at the match field. Output follows the first branch's order |
Recommended default: keep each branch separate
For most users, especially when connecting integration nodes like Birdeye, use Keep each branch separate.
Example workflow:
Manual Trigger → Birdeye SOL price ┐
├→ Merge → Telegram
Manual Trigger → Birdeye BONK price┘Set the Birdeye nodes' response names to something readable:
Birdeye SOL price → solPrice
Birdeye BONK price → bonkPrice
Merge → mergeResponseWith Keep each branch separate, Merge outputs:
{
"success": true,
"mode": "namespacedObject",
"inputCount": 2,
"result": {
"solPrice": {
"success": true,
"operation": "getPrice",
"address": "So111...",
"data": { "value": 142.31 }
},
"bonkPrice": {
"success": true,
"operation": "getPrice",
"address": "DezX...",
"data": { "value": 0.000018 }
}
},
"meta": {
"inputs": [
{ "responseName": "solPrice", "type": "object" },
{ "responseName": "bonkPrice", "type": "object" }
]
}
}A downstream Telegram, Discord, AI, Transform, Code, or Condition node can reference:
SOL: {mergeResponse.result.solPrice.data.value}
BONK: {mergeResponse.result.bonkPrice.data.value}If two upstream nodes have the same response name, Merge keeps both by adding suffixes such as birdeyeResponse and birdeyeResponse_2. That works, but readable response names such as solPrice and bonkPrice are easier to use.
Combine matching fields
Combine matching fields requires every incoming branch to output an object. It spreads the fields into one object.
Example inputs:
{ "symbol": "SOL", "price": 142.31 }
{ "volume24h": 1000000, "liquidity": 500000 }Merged value inside result:
{
"symbol": "SOL",
"price": 142.31,
"volume24h": 1000000,
"liquidity": 500000
}Be careful with raw integration outputs. Many integration nodes return similar wrapper fields such as success, operation, and data. If two branches both have data, the later branch overwrites the earlier branch's data. Use Keep each branch separate when you need to preserve both full responses.
Stack lists together
Stack lists together requires every incoming branch to output an array. It appends all items into one list.
Example inputs:
[
{ "symbol": "SOL" },
{ "symbol": "BONK" }
][
{ "symbol": "JUP" }
]Merged value inside result:
[
{ "symbol": "SOL" },
{ "symbol": "BONK" },
{ "symbol": "JUP" }
]Reference the first item downstream with:
{mergeResponse.result[0].symbol}Envelope auto-unwrap
The three list modes - Stack lists together, Match list items by position, and Match list items by field - accept the standard executor envelopes around an array directly:
{ success: true, data: [...] }(Transform, Code, AI nodes, most integrations){ success: true, result: [...] }(For Each End, Merge itself)
Merge sees through the wrapper to the inner array, so a Transform or Code node in between is no longer required. The meta.unwrappedFromEnvelope field on Merge's output records which incoming branches were auto-unwrapped, so the behavior stays visible.
You still need a Transform step when the array is nested deeper, for example to pull data.tokens out of { success, data: { tokens: [...] } }. Auto-unwrap is intentionally one level - anything further is a real reshape that belongs in Transform.
Match list items by position
Match list items by position requires every incoming branch to output an array. It combines items with the same index.
Example inputs:
[
{ "address": "A", "symbol": "AAA" },
{ "address": "B", "symbol": "BBB" }
][
{ "price": 1.25 },
{ "price": 2.5 }
]Merged value inside result with Per-Item Combine set to Combine fields:
[
{ "address": "A", "symbol": "AAA", "price": 1.25 },
{ "address": "B", "symbol": "BBB", "price": 2.5 }
]The output length is the shortest input list. If one list has 10 items and another has 8, the merged result has 8 items.
Use this only when the lists are already in the same order.
Match list items by field
Match list items by field requires every incoming branch to output an array of objects. It joins items where all branches share the same value at the configured Match Field.
Example configuration:
Mode: Match list items by field
Match Field: address
Per-Item Combine: Combine fieldsExample inputs:
[
{ "address": "A", "symbol": "AAA" },
{ "address": "B", "symbol": "BBB" }
][
{ "address": "B", "price": 2.5 },
{ "address": "A", "price": 1.25 }
]Merged value inside result:
[
{ "address": "A", "symbol": "AAA", "price": 1.25 },
{ "address": "B", "symbol": "BBB", "price": 2.5 }
]The output follows the order of the first incoming branch. Items without a match in every branch are dropped and counted in meta.droppedFromInputs (one count per incoming branch). Duplicate join keys are counted in meta.duplicateKeysPerInput so you can debug unexpected drops.
Match fields can use dotted paths:
address
data.mint
token.idField paths cannot be empty, start or end with ., contain .., or use reserved segments such as __proto__, constructor, or prototype.
Per-item combine
For Match list items by position and Match list items by field, choose how matched items become one row:
| Option | Output shape | Use when |
|---|---|---|
| Combine fields | { "symbol": "SOL", "price": 142 } | Fields do not conflict, or you are okay with later fields overwriting earlier ones |
| Keep separate | { "tokens": { ... }, "prices": { ... } } | You need to preserve each branch exactly |
If you choose Keep separate, each matched item is nested under the upstream branch's response name. If Match list items by position is set to Combine fields but a row contains a non-object item, that row automatically falls back to the same nested shape so the run does not silently drop the value. The fallback row indices are recorded in meta.autoNamespacedIndices.
Match list items by field does not fall back: if any array element is not an object, the node fails with a clear error so you can fix the upstream shape.
Output
A successful Merge node returns:
{
"success": true,
"mode": "namespacedObject",
"inputCount": 2,
"result": {},
"meta": {
"inputs": [
{ "responseName": "branchA", "type": "object" },
{ "responseName": "branchB", "type": "array", "length": 12 }
]
}
}Downstream nodes normally read from .result:
{mergeResponse.result}
{mergeResponse.result.solPrice.data.value}
{mergeResponse.result[0].address}The meta object is for debugging. The exact fields depend on the mode:
| Field | Modes | What it tells you |
|---|---|---|
inputs | All | One entry per incoming branch: { responseName, type, length? } |
outputLength | Stack lists, Match by position, Match by field | Number of items in the merged list |
lengthMismatch | Match list items by position | true when input arrays were not all the same length |
autoNamespacedIndices | Match list items by position | Row indices that auto-fell-back to nested shape because a row had a non-object item |
droppedFromInputs | Match list items by field | One count per branch - items that did not survive to the merged output (missing key, no match, or a sibling branch lacked the key) |
duplicateKeysPerInput | Match list items by field | One count per branch - items skipped because their join key was already seen in that branch (first-write-wins) |
key | Match list items by field | Echo of the resolved match field |
unwrappedFromEnvelope | Stack lists, Match by position, Match by field | One entry per branch whose { success, data | result: [...] } envelope was auto-unwrapped to the inner array - { responseName, from }. Omitted when no unwrap occurred |
Limits and safety
- Merge needs at least one incoming branch. One branch is valid, but two or more branches are recommended for real merging.
- Duplicate edges from the same source node count as one input.
- Routing-skipped branches are not merged.
- Mode-specific type checks are strict. Array modes fail if an incoming branch is neither an array nor a
{ success: true, data | result: [...] }envelope around one. Unknown values for Mode or Per-Item Combine fail the run rather than falling back silently. - Keep each branch separate and namespaced per-item modes require safe response names: start with a letter or digit and contain only letters, digits,
_, or-. - Avoid response names that start with
_or$, contain., or equal__proto__,constructor, orprototype. - Merge errors are treated as failed node runs, not soft-success responses.
When to use Merge vs Transform vs Code
Use Merge when you have incoming branches and want one downstream node to read their outputs together. It is designed for two or more branches; with one branch, the output still follows the selected Merge mode.
Use Transform before Merge when each branch needs to extract or reshape data first, for example turning { success, data: { tokens: [...] } } into a bare token array.
Use Code when the combine logic needs custom JavaScript, complex grouping, custom scoring, or behavior that does not fit the Merge modes.
