teachyou.ai academy
← All posts
n8n

n8n Merge Node Explained: Combining Data from Multiple Branches

Pramod Dutta · Jun 7, 2026 · 14 min read

Why the Merge node trips up so many builders

If you have spent any real time in n8n, you have probably hit a moment where two branches of your workflow need to become one again. Maybe you split a workflow to fetch data from an API in one branch and a database in another, and now you need both results side by side. Maybe you ran a loop that produced ten different outputs and you need them combined into a single list before sending a summary email. This is exactly the job of the Merge node, and it is also one of the most misunderstood nodes in the entire n8n toolkit.

The confusion is understandable. The Merge node changed significantly between older and newer versions of n8n, the terminology around "modes" is not always intuitive, and the visual layout of multiple input branches feeding into one node can look deceptively simple while hiding real complexity underneath. Get it wrong and you end up with duplicated rows, missing fields, or a workflow that silently drops data because it matched on the wrong key.

This article walks through what the Merge node actually does, the different modes it supports, how matching works under the hood, and the mistakes that most commonly break automations built on top of it. By the end, you should be able to look at any two branches in your workflow and know exactly which Merge mode will get you the result you want.

What the Merge node is actually for

At its core, the Merge node takes data from two or more input connections and produces a single combined output. That is the entire premise. But "combined" can mean several very different things depending on what you are trying to achieve:

  • You might want to stack two lists on top of each other (append)
  • You might want to join rows together side by side based on a shared field, similar to a SQL join (combine by matching fields)
  • You might want to keep only the items that exist in both branches (matching, keep matches)
  • You might want to find items that exist in one branch but not the other (matching, keep non-matches)
  • You might want to simply pick whichever branch finishes first (choose branch)

Each of these is a distinct mode inside the node, and choosing the wrong one is the single biggest source of Merge node bugs. The node itself won't stop you from picking the wrong mode — it will run happily and produce output, just not the output you expected. This is why understanding the modes conceptually, rather than just clicking through the dropdown, matters so much.

It also helps to remember that the Merge node does not know anything about your business logic. It does not know that "customer_id" and "cust_id" are supposed to represent the same thing across two data sources. It only works with what you explicitly tell it to compare. Every subtle bug in a Merge node setup traces back to a mismatch between what you assumed the node understood and what you actually configured.

Setting up two branches to feed into Merge

Before you can use the Merge node, you need two (or more) branches of data flowing toward it. In n8n, this usually happens in one of two ways.

The first is a natural split further upstream, where an IF node or Switch node routes items down different paths based on a condition, and those paths eventually need to reconverge. The second, more common pattern for Merge specifically, is when you deliberately run two independent operations in parallel — for example, one branch calls a CRM API to fetch customer details while a second branch queries your own database for order history on the same customer.

To wire this up, you drag two separate connections into the Merge node's two input handles. In the n8n canvas, the Merge node visually shows two (or more) small connector dots on its left side, and each incoming branch attaches to one of them. The order matters more than people expect: Input 1 and Input 2 are treated differently depending on the mode. In "combine" mode, Input 1 is typically treated as the primary/left side, and Input 2 is the "lookup" side used to enrich it. Getting these swapped is a common source of confusing partial results.

A helpful mental model: think of Input 1 as "the main list I want to keep" and Input 2 as "the data I want to attach to it." That framing holds true across most of the matching-based modes.

Append mode: stacking lists together

Append is the simplest mode to understand. It just concatenates the items from all input branches into one list, one after another, with no attempt to match anything.

Use this mode when you have run the same type of operation across different sources and you just want one unified list at the end. A classic example is scraping data from three different websites in three parallel branches, where each branch returns "articles," and you want one big list of articles regardless of source.

Branch A: [item1, item2, item3]
Branch B: [item4, item5]

Append output: [item1, item2, item3, item4, item5]

The catch with Append is that it does not deduplicate and does not align fields between branches. If Branch A's items have a field called title and Branch B's items have a field called headline, Append will not reconcile that difference — you will end up with a list where half the items have title populated and the other half have it empty, and vice versa for headline. Append is purely mechanical stacking, so any field-name harmonization needs to happen before the Merge node, typically with a Set node or an Edit Fields node placed in each branch beforehand.

Combine mode: joining data by matching fields

This is where the real power (and real complexity) of the Merge node lives. Combine mode works like a database join: it looks for a field (or combination of fields) that exists in both input branches, and it stitches together the items where that field's value matches.

Say Branch 1 returns a list of orders with an email field, and Branch 2 returns a list of customer profiles also carrying an email field. Using Combine mode with "email" as the matching field, the Merge node produces one item per matching pair, containing fields from both the order and the customer profile.

Branch 1 (orders):
  { email: "a@x.com", orderTotal: 120 }
  { email: "b@x.com", orderTotal: 75 }

Branch 2 (customers):
  { email: "a@x.com", name: "Asha" }
  { email: "b@x.com", name: "Ben" }

Combine output (matched on email):
  { email: "a@x.com", orderTotal: 120, name: "Asha" }
  { email: "b@x.com", orderTotal: 75, name: "Ben" }

Inside Combine mode, n8n gives you a few join-type options that map closely to SQL join semantics:

  1. Keep only matches (inner join) — only items present in both branches survive
  2. Keep matches and unmatched from Input 1 (left join) — everything from the first branch stays, with data from the second branch attached wherever a match exists
  3. Keep everything (outer join) — every item from both branches is kept, matched where possible, standalone where not

If you have worked with SQL before, this will feel immediately familiar. If you have not, the key thing to internalize is: "matching" means "same value in the field you selected," not "same meaning." A trailing space, a different capitalization, or a numeric field on one side and a string on the other side will all cause a match to silently fail. This is worth testing explicitly rather than assuming it will "just work" the first time.

Matching by multiple fields, not just one

Real-world data rarely joins cleanly on a single field. Often you need to match on a combination — say, email AND orderDate, because email alone might repeat across multiple orders. The Merge node supports adding multiple matching fields inside the same Combine configuration.

When you add more than one matching field, n8n requires all of them to match for two items to be considered the same. This is an AND condition, not an OR condition. If you need OR-style matching logic (match on either email or phone number), the Merge node alone will not do this for you — you typically need to normalize your data first so that a single reliable key exists on both sides, often using a Code node or a Set node to compute a synthetic composite key before the items reach the Merge node.

// Example: building a composite key before merging,
// inside a Code node placed before the Merge node
for (const item of $input.all()) {
  item.json.matchKey = `${item.json.email}-${item.json.orderDate}`;
}
return $input.all();

Doing this kind of key normalization upstream saves you from chasing "why didn't this match" bugs after the fact. It also makes your workflow easier to debug because you can inspect the matchKey field directly in the execution data.

Matching mode: finding differences instead of combining fields

A separate mode worth calling out is the "Matching" configuration used specifically to find overlaps or gaps between two datasets, without necessarily merging fields together. This is subtly different from Combine, because sometimes you don't want to enrich data — you want to know which records are missing.

A very common real-world use case: you have a list of leads from a marketing tool in Branch 1 and a list of contacts already in your CRM in Branch 2. You want to know which leads are new (not yet in the CRM) so you can create them, and which already exist so you can update them instead of duplicating them.

  • "Keep matches" tells you which leads already exist in the CRM
  • "Keep non-matches" (sometimes labeled as items only in Input 1) tells you which leads are brand new

This pattern is the backbone of a huge number of sync workflows: syncing a spreadsheet to a database, syncing form submissions to a CRM, or deduplicating a mailing list against an existing subscriber table. Once you see it framed this way, you will likely notice you have several existing workflows that could be simplified by using this mode instead of writing custom comparison logic in a Code node.

Choose branch mode and other simpler options

Not every use of the Merge node is about joining data at all. Sometimes you have two branches that represent alternative paths — for example, a "primary API" branch and a "fallback API" branch — and you only want whichever one actually produced data.

The "choose branch" style option (in some n8n versions exposed as choosing Input 1 or Input 2 output, or picking whichever has data) lets you collapse a fallback pattern into a clean single output, rather than manually writing an IF node afterward to figure out which branch actually ran. This is especially useful in error-handling patterns where Branch 1 attempts a primary integration and Branch 2 is only populated if the first one fails and a fallback step kicks in.

It is a smaller, less-discussed feature compared to Combine, but it solves a real structural problem: without it, you often end up with awkward downstream logic full of "if branch 1 is empty, use branch 2" checks scattered across multiple nodes.

Common mistakes that break Merge node workflows

Almost every Merge-related bug report falls into one of a handful of categories. Knowing them ahead of time will save you hours of debugging.

  • Mismatched field types. A numeric ID on one branch and a stringified ID on the other will never match, even though they look identical when you eyeball them in the execution log.
  • Case sensitivity and whitespace. Emails like Test@x.com and test@x.com are different strings unless you explicitly normalize casing before merging. Trailing spaces from spreadsheet imports are a frequent silent culprit.
  • Wrong input order assumptions. Swapping which branch is plugged into Input 1 versus Input 2 changes which side is treated as the "base" list in left-join style configurations.
  • Expecting deduplication from Append. Append never deduplicates. If you need unique items, you need a separate step (often a Code node or a "Remove Duplicates" node) after merging.
  • Forgetting that unmatched items may still appear (or disappear). Depending on whether you picked inner, left, or outer join behavior, unmatched items are either dropped or passed through with empty fields — and it is easy to configure this without realizing the downstream impact until real data reveals it.
  • Assuming Merge waits for both branches to fully finish before running once. Depending on workflow structure, one branch can trigger the Merge node prematurely if the other branch has not yet produced output, particularly in workflows with loops or asynchronous webhook triggers. Testing with realistic data volume, not just a single sample item, exposes this quickly.

A practical habit that solves most of these problems in one move: add a small Code node (or Set node) right before each Merge input to log or inspect the exact shape of the data, including field names and types, using the "Pin data" feature in n8n so you can compare both branches side by side before wiring them into Merge. Five minutes of inspection here regularly saves an hour of confused debugging later.

A worked example: enriching orders with customer loyalty data

To make this concrete, imagine a workflow that needs to send a personalized email whenever an order is placed, including the customer's current loyalty tier. The order data comes from a webhook (Branch 1), and the loyalty tier comes from a separate database lookup keyed by customer ID (Branch 2).

The setup looks like this:

  1. A Webhook node receives the order payload and extracts customerId and orderTotal
  2. In parallel, an HTTP Request or database node looks up the loyalty tier using that same customerId
  3. Both branches feed into a Merge node configured in Combine mode
  4. The matching field is set to customerId on both sides
  5. The join type is set to "keep matches and unmatched from Input 1" so that even customers without a loyalty record still get an email, just without a tier badge
  6. The merged output, now containing both orderTotal and loyaltyTier in a single item, feeds into an email-sending node that references both fields in the message template
Input 1 (order): { customerId: "C102", orderTotal: 249 }
Input 2 (loyalty): { customerId: "C102", loyaltyTier: "Gold" }

Merged: { customerId: "C102", orderTotal: 249, loyaltyTier: "Gold" }

Notice how the entire personalization logic depends on picking the correct join type in step 5. An inner join here would silently skip emailing any customer without a loyalty record, which might be the opposite of what the business actually wants. This is exactly the kind of decision that looks trivial in the node's settings panel but has real consequences in production.

Testing your Merge configuration before trusting it

Before shipping any workflow that relies on the Merge node, run it manually with pinned test data that intentionally includes edge cases: an item that should match, an item that should not match, and an item with a slightly malformed field (extra whitespace, different capitalization, missing value). Inspect the actual output JSON for each case rather than assuming the configuration is correct because the workflow "ran without errors." The Merge node will not throw an error when a match fails to happen — it will just quietly produce a different shape of data than you expected, and that is the kind of bug that surfaces in production, not in testing, if you skip this step.

It is also worth re-testing your Merge configuration any time an upstream node changes its output fields, even slightly. A field rename in an API response, a schema change in a database table, or a modified Set node earlier in the workflow can all silently break a matching configuration that used to work fine.

Wrapping up

The Merge node is one of those n8n building blocks that looks deceptively simple on the canvas but carries real decision-making weight underneath. Append is for stacking, Combine is for joining and enriching, the matching-focused configuration is for finding overlaps and gaps, and the branch-selection option is for cleanly collapsing fallback paths. Picking the right one, matching on the right fields with the right normalization, and testing with deliberately messy sample data will save you from the majority of bugs that show up in real automations built around this node.

If you want to go deeper into building reliable, production-grade automations in n8n — including advanced data-shaping patterns like this one, working with AI-driven decision logic, and wiring up multi-branch workflows that don't break the moment your data gets messy — check out the n8n AI Agent Tutorial course on teachyou.ai, where we build these patterns from scratch step by step.