Chapter 16

Merge / Loop Over Items / Split Out / Aggregate: working with many items

The last chapter, IF / Switch, was about splitting — one path becoming two. This chapter goes the other way: joining two paths into one, turning a single run over 500 items into rounds of 5, and breaking the big array an API hands back into separate items. These nodes are the plumbers of n8n, and almost every real workflow needs them. A note on names: this chapter uses the current official names. The old Split In Batches is now Loop Over Items (the editor shows the node as "Loop Over Items (Split in Batches)"), and the old Item Lists has been broken into the separate nodes Split Out, Aggregate, Sort, Limit, Remove Duplicates and Summarize — search for the new names.

Why these three nodes matter

Once you have built a few workflows you will run into these situations:

  • You split with IF and cannot work out how to join the paths back. The IF node splits the data down a true / false pair of paths. Each side finishes its own work and you want one summary notification at the end — but the two paths keep running separately, and nothing you try brings them back together.
  • The Airtable / Notion API allows 5 calls a second and you feed it 500 items at once. Push them all in and the node is knocked back with 429 Too Many Requests halfway through: the early ones went in, the rest never ran, and patching that up is painful.
  • The API returns one item with a results array inside, and the nodes downstream cannot reach it. You saw this trap in Chapter 9 — { total: 5, items: [...] } counts as 1 item, so the Slack node downstream sends once instead of 5 times.
  • Two APIs each return half the data and you need a join to get the whole picture. One side has the customer details, the other the order amounts, both on the same customer_id, and you want them stitched into one report.

These four problems are solved by a handful of nodes: Merge (joining paths), Loop Over Items (formerly Split In Batches, for batching), Split Out (an array becomes several items) and Aggregate (several items become one array) — the last two used to be Item Lists and are separate nodes now. Learn these and your workflows move on from "toys" to "things the business can run on".

Chapter goal: by the end you can tell these nodes apart, know how Merge's four modes (Append / Combine / SQL Query / Choose Branch) differ, use Loop Over Items with a Wait node to stay under a rate limit, use Split Out to expand an array into several items, and use Aggregate to collect several items into one summary. The hands-on examples build two small workflows, both of which you can copy as-is and adapt.

What each node does, in one table

Get each node's place straight first, and you will know which one to reach for as soon as a situation shows up:

NodeWhat it solvesInputOutputClassic use
Merge Joins two branches into one, or joins them on a key. Two (Input 1 / Input 2) One items array Rejoining after an IF split to carry on, joining data from two APIs.
Loop Over Items
(formerly Split In Batches)
Turns one run of N items into several rounds of M. One (a lot of items) Two: loop (this round's items) / done (everything finished) API rate limits, running out of memory, wanting a progress message per batch.
Split Out
(formerly Item Lists · Split Out Items)
Expands an array field inside an item into several items. One Many (the array flattened) An API returning { items: [...] }, a Gmail attachment array.
Aggregate
(formerly Item Lists · Aggregate Items)
Collapses several items into an array inside a single item. Many One Report roll-ups, sending one daily summary instead of many.
Sort / Limit / Remove Duplicates / Summarize Sort / take the first N / deduplicate / summarize. Many Many or one The other operations from the old Item Lists, each its own node now.
Tip: if you know SQL: Merge's Combine → Matching Fields is a JOIN, Merge Append is UNION ALL, Split Out is UNNEST, Aggregate is GROUP BY collected into an array, and Summarize is GROUP BY with aggregate functions. Only the names change; the mental model is the same.

The Merge node: four modes, and three more choices under Combine

The Merge node always needs two inputs (you see two connector dots when you draw a connection to it). The Mode dropdown has four options — this is the structure n8n moved to after 2024, and it is not the "three modes" older tutorials describe:

ModeWhat it doesNotes
Append Keep data from all inputs: stacks the items from both sides one after the other. Input 1 = 3 items, Input 2 = 2 items, output = 5 items. Like pasting rows under rows in Excel.
Combine Combine data from two inputs: joins the fields from both sides side by side, then you pick one of three Combine By options (see the next table). The most-used merge mode. It replaces the old Combine by Position / by Key / Multiplex that used to be listed directly.
SQL Query Write SQL (SELECT ... FROM input1 JOIN input2 ...) to define the merge rule yourself. For complex JOINs and complex WHEREs — anything the UI cannot express goes here.
Choose Branch Keeps only one side: "of the two branches, carry this one's data on downstream". Not a merge, a choice between two. It replaces the old trick of rigging this up with two IF nodes.

Once you pick Combine, there are three strategies under Combine By:

Combine ByWhat it doesInput 1Input 2Output
Matching Fields Compare items by field values: pairs items on a key value, the same as a SQL JOIN. Customer data (on id) Order data (on customer_id) Output Type decides whether unmatched items are kept
Position Combine items based on their order: aligns by position in the array and joins the fields. [{a:1},{a:2},{a:3}] [{b:x},{b:y},{b:z}] [{a:1,b:x},{a:2,b:y},{a:3,b:z}]
All Possible Combinations Output all possible item combinations: the Cartesian product (cross join / multiplex). 3 items 2 items 3 × 2 = 6 items

With Matching Fields selected, Output Type decides the kind of join (all five are there):

  • Keep Matches — only items matched on both sides are output (inner join).
  • Keep Non-Matches — only unmatched items are output (for finding the difference, as in "which customers have never ordered").
  • Keep Everything — everything is kept, and unmatched fields come out as null (full outer join).
  • Enrich Input 1 — Input 1 leads, and the fields from Input 2 that match are pasted on (left join).
  • Enrich Input 2 — Input 2 leads, and the fields from Input 1 that match are pasted on (right join).
Concept: the Merge node only runs once something has arrived at both inputs. So when you rejoin after an IF split, both paths have to reach the two Merge inputs. If one path is "do nothing", wire a No Operation, do nothing node into Merge anyway, or Merge waits forever.

Loop Over Items (formerly Split In Batches): batch the work to stay under rate limits

The situation: the Airtable API allows at most 5 requests a second, and your Google Sheet has 500 customer rows to write into Airtable. Wire it straight through and after the 5th item you start collecting 429s, with the remaining 495 all failing. This is where Loop Over Items comes in.

Naming, once and for all: this node used to be called Split In Batches; since 2024 the editor shows it as Loop Over Items (Split in Batches) — searching Loop, Split or batches all find it, and it works exactly the same. The splitInBatches type in older workflow JSON stays compatible and needs no change.

Its behavior is a little unusual and confusing the first time you meet it: this node forms the loop itself.

Setting / fieldWhat it doesCommon values
Batch SizeHow many items each round sends to the loop output.5 for Airtable, 10-50 for a typical API.
Options → ResetWhether to reset the counter when new items arrive.Usually left off for a one-shot workflow; turn it on when the workflow is called continuously.
Output: loopThe items in this batch (wire the processing nodes downstream of it).Feeds Airtable / HTTP Request.
Output: doneTaken only after everything has run, and only once.Feeds an "all finished" notification or roll-up.

On the canvas it looks like this (the nodes on the loop side have to draw a connection back to the Loop Over Items node, which is what makes the loop):

Google Sheets (500 items)
  ↓
Loop Over Items (Batch Size = 5)
  ├─ loop → Airtable Create → Wait 1 second ──┐
  │                                            │
  │←─────── back to the Loop Over Items node ──┘
  │
  └─ done → Slack "500 records processed"
Warning: the Wait node is the partner that makes this work. 5 calls a second on the API, Batch Size 5, Wait 1 second, and you run steadily just under the rate limit. Without a Wait node you have batches but no pause between them, so all 5×N requests go out almost at once and it still blows up.

Loop Over Items does not produce a "progress bar" message itself, but every round of the loop passes through the same nodes — to report progress along the way, add a Slack node on the loop branch that sends "batch N done, M batches left".

Split Out / Aggregate: flatten an array, or collect items back into one

Naming, once and for all: n8n used to have an "Item Lists" node holding six operations in one: Split Out Items / Aggregate Items / Sort / Limit / Remove Duplicates / Summarize. It has since been broken into six separate nodes, named Split Out, Aggregate, Sort, Limit, Remove Duplicates and Summarize. Type the new name when you search for a node; an older workflow still runs when you import it, but Item Lists is gone from the current UI.

Split Out: an array inside one item → several items

Classic use: the Gmail node picks up a message whose binary or json holds an attachments array with 5 attachments. The whole thing counts as 1 item, and you want to run an upload to Google Drive once per attachment.

SettingValueWhat it means
Fields To Split Outattachments (or the path to that array; comma-separated for several)Names the field holding the array to split.
IncludeNo Other Fields / All Other Fields / Selected Other FieldsWhether each item that comes out carries the original item's other fields (the mail subject, for example).
Options → Destination Field NameLeave it empty, or name a new fieldWhich key each split-out payload sits under. Leave it empty and it is flattened.
Options → Disable Dot NotationOff by defaultTurn it on when a field name itself contains a ., so it is not read as a nested path.

After the split the output goes from 1 item to 5 items, and every node downstream runs 5 times. This is the answer to the point in Chapter 9 that "n8n items and a JSON field called items are two different things" — Split Out turns a JSON array into n8n items.

Aggregate: several items → one array inside a single item

The opposite operation. Five items have been processed and you want to collapse them into one daily summary sent as a single email. Add an Aggregate node and pick one of the two Aggregate options:

  • Individual Fields — aggregates only the fields you name, with Rename Field to change the name and Merge Lists to flatten nested arrays.
  • All Item Data (Into a Single List) — packs every item's whole json into one big array, with Include / Exclude lists to allow or block fields.

Sort (sorting, SQL ORDER BY), Limit (take the first N, SQL LIMIT), Remove Duplicates (deduplication, SQL DISTINCT) and Summarize (grouped statistics, SQL GROUP BY plus aggregate functions) are each their own node now too — search for whichever one you need.

Worked example: one Slack reminder per Google Sheet row

This workflow shows the case where "the items are expanded already, so you do not need Loop Over Items at all" — Google Sheets Read turns each row into an item by itself. Treat it as a warm-up.

  1. Add a Manual Trigger

    Create a workflow and pick Trigger manually as the first node. Pressing Execute Workflow then runs it once.

  2. Add a Google Sheets node

    Wire on a Google Sheets node and set Operation to Get Rows in Sheet. Connect the credentials (Chapter 2), then pick your spreadsheet and sheet. Say the spreadsheet is called "Customer list", has 10 rows, and its fields are name, email and due_date.

  3. Execute step and confirm 10 items arrive

    Press Execute step on the Google Sheets node. The top of the output panel on the right shows 10 items — check the count is right. Switch to the Table view to read each row.

  4. Add a Slack node

    Wire on a Slack node, set Operation to Send Message and pick a channel (#billing-reminder, for example). Switch the Text field to Expression mode and enter:

    Statement reminder for {{ $json.name }}. Due {{ $json.due_date }} — please take care of it.
  5. Execute the whole workflow once

    Press Execute Workflow at the top right. The Slack node's output panel shows 10 items — 10 messages sent, one per row. Check the channel to confirm 10 actually arrived.

  6. Save it

    Save at the top right. From then on, pressing Execute Workflow sends another round. To send it automatically on the 1st of each month, swap the Manual Trigger for a Schedule Trigger (Chapter 10).

Tip: this example uses none of this chapter's nodes — Google Sheets Read already turns 10 rows into 10 items. "Naturally many items" and "many items inside an array" are two different things, and the next example is the second one.

Worked example: split the array an API returns and act on each entry

This is where Split Out belongs. Call a public API (https://jsonplaceholder.typicode.com/users, for example) and it returns one whole array of 10 users — what n8n receives is 1 item whose field is an array. To send a Slack notification per user, you need Split Out.

  1. Manual Trigger + HTTP Request

    Pick a Manual trigger. Wire on an HTTP Request node, set Method to GET and URL to https://jsonplaceholder.typicode.com/users. Run it once with Execute step.

  2. Check the output: 1 item with an array inside

    The top of the output panel shows 1 item (not 10). Switch to the JSON view and expand it: the whole response is an array. As it stands, any node downstream would run once — wrong, we want it to run 10 times.

  3. Add a Split Out node

    Wire on a Split Out node (search "split out" and it comes straight up; the "Item Lists → Split Out Items" of older tutorials is this standalone node now). Put the field holding the array in Fields To Split Out — if HTTP Request puts the whole array in data or at the root, use the path that matches (read the real path off the output panel when you test it).

  4. Execute step and confirm 10 items

    Press Execute step on the Split Out node. The top of the output panel shows 10 items — one row per user. Switch to the Table view and check that name and email both have values.

  5. Wire on a Slack node to notify per item

    Wire on a Slack node with Send Message and set Text to Welcome {{ $json.name }} ({{ $json.email }}). Run Execute Workflow once → the Slack channel receives 10 welcome messages.

  6. (Advanced) Aggregate back into one summary

    Add an Aggregate node after the Slack node, set Aggregate to Individual Fields and Field To Aggregate to name (Rename it to names if you like). The output is back to 1 item, with a field holding an array of 10 names. Then wire on a Gmail node that sends "Processed these 10 users today: {{ $json.names.join(', ') }}" — one summary email, instead of 10 alerts flooding the inbox.

Warning: Fields To Split Out has to hold "the path of the array field itself", not a field inside the array. Getting it wrong is the most common trap — the output comes out as 1 item, the same as the input, so the node looks like it did nothing. Switch to the Schema view on the upstream node, read the path carefully, then fill it in.

Four common combinations

Knowing a single node is only the start; the real strength is in "two or three of them together". You will reach for these combinations most weeks:

CombinationWhat it solvesStructure
IF + Merge Each branch does its own thing, then they rejoin into one path that continues downstream. IF → true branch (Set A) & false branch (Set B) → Merge (Append) → Slack
Loop Over Items + Wait Batching plus a pause, to stay under a rate limit. Loop Over Items → HTTP Request → Wait 1s → back to Loop Over Items
Split Out + processing + Aggregate One big array from an API → do something with each entry → collapse into one summary. HTTP Request → Split Out → processing nodes → Aggregate → Email
Merge Combine · Matching Fields Two data sources joined on a key into one complete record. HTTP A →     ↓
HTTP B → Merge (Mode = Combine, Combine By = Matching Fields, id ↔ customer_id) → downstream

Every one of these is worth trying on the practice workflow from Chapter 8 first: get it running there before you point it at real business work.

AI×Odoo multi-branch workflow with a Merge node joining the branches
Figure 16-1 A real example: an AI×Odoo workflow of 14 nodes, where several branches finish and a Merge node brings them back to the main line for the downstream work.

Common pitfalls

  1. Merge Combine · Position loses half the data

    Combine By = Position means "align by position" — with 3 items on Input 1 and 5 on Input 2, the output has only 3 (the shorter side wins) and the last 2 are simply dropped. To keep everything, switch to Combine By = Matching Fields with Output Type Keep Everything, or use Append mode to stack them and work from there.

  2. Loop Over Items never stops and runs forever

    Two common causes: (a) the nodes on the loop branch are not wired back to the Loop Over Items node — the loop runs one round, the rest of the data is dropped, and nothing actually errors; wire it to the done branch by mistake instead and things get strange; (b) Options → Reset is on while new items keep arriving upstream (a Webhook trigger, for example), so each arrival counts as a fresh round. Leave it off unless you are building a long-running service.

  3. Split Out runs and the output is identical

    Nine times out of ten Fields To Split Out is wrong: (a) the field name is misspelled (case, singular vs plural); (b) you entered a field inside the array rather than the array itself; (c) the field is not an array at all but an object. Switch to the Schema view on the upstream node, find the field whose type is array, and paste its full path (data.results, for example) in.

  4. The item order changes after a Merge

    Each mode has its own ordering rule: Append guarantees all of Input 1 first and all of Input 2 after; Combine · Position guarantees the same index lines up; Combine · Matching Fields orders by the order the keys appear in Input 1 — if you need a particular order, put a Sort node before or after the Merge.

  5. The Merge node sits there and never runs

    Merge only runs when both inputs have data. After an IF split, if no item goes down the true branch (every condition came out false), Merge waits forever. The fix: put a No Operation, do nothing node on the empty branch (it changes no data, it just carries the connection), or use the IF node's Fallback Output so something reaches both Merge inputs.

  6. Loop Over Items with a Wait, but the wait is not what you expected

    The seconds on a Wait node mean "this item waits N seconds here before it moves on" — with Batch Size = 5, the 5 items in one batch reach Wait at nearly the same moment and then each waits 5 seconds (not one 5-second wait for the batch). For "5 seconds between batches", keep Batch Size at 5 and Wait at 5, then test it and read the timestamps of the requests that actually went out.

FAQ

Does Merge always need two inputs, or can it take three or more?
Two by default. To merge three or more, the most direct way is to chain two Merge nodes — A and B go into the first Merge, its output plus C go into the second, and so on. The Merge node itself has no connectors for three or more inputs.
How large can Batch Size be on Loop Over Items?
There is no hard technical ceiling. In practice start at 10-50 and stay under 500. Too small (1, say) and the node runs hundreds of rounds, slowing the whole workflow down; too large (5000, say) and you lose the point of batching while eating memory. The rule: sit right up against the downstream API's rate limit, or the per-batch limit of its batch API. The Airtable batch endpoint takes at most 10 records at a time, so set Batch Size to 10.
Split Out or the Code node for splitting an array — which is better?
For a plain "expand the array in this field into several items", Split Out is enough and needs no code. When the split itself needs conditions, type conversion, filtering or computed fields, or when the array has an odd shape (arrays inside arrays), use the Code node (Chapter 20). The principle: do not write code when you do not have to; a built-in node always comes first.
Is an n8n workflow multi-threaded — can it process several items at once?
Inside a single workflow execution, items are processed in sequence — a node handling 100 items "runs 100 times", it does not run "100 threads at once". For the "do not wait for the previous item" effect there are two routes: (a) use Loop Over Items to cut the work into small pieces that run separately; (b) self-host n8n with several workers (EXECUTIONS_MODE=queue) so several workflow executions run at the same time. Parallelism inside one execution is not the direction n8n is designed in.
What if I only want to try the first 10 of the 500 items?
The simplest way: wire a Limit node after the data source (formerly the Limit operation in Item Lists, its own node now) and set Max Items to 10. Before you go live, disable the Limit node (right-click it and choose Deactivate) or delete it. It is a good habit while you are developing, and it keeps a test from really sending 500 emails.
What decides a match in Merge Combine · Matching Fields? Is it case-sensitive?
n8n compares with strict equality: same type and same value counts as a match. Strings are case-sensitive — "Alice" and "alice" do not match. Types matter — the string "123" and the number 123 do not match either. When the types disagree, line them up with a Set node first (cast to string or to number) before Merge; or in Merge's SQL Query mode, write a CAST.
What happens if Loop Over Items fails halfway — does the work already done roll back?
Nothing rolls back — n8n has no concept of a transaction. Whatever was written into Airtable / Slack stays written, and a failure on batch N only affects what comes after N. What to do: (a) add Error Trigger / continueOnFail on the loop branch (Chapter 17) so one failed item is logged instead of stopping the run; (b) deduplicate before you process, so a rerun does not create duplicate data.
When should I use Merge's SQL Query mode?
When the three Combine By strategies in the UI cannot do what you need, switch to SQL Query: a join on several fields (ON a.id = b.customer_id AND a.region = b.region), a CASE WHEN, a UNION with a WHERE filter, a subquery. n8n runs it with alasql underneath, and the two inputs are the tables input1 / input2. For a plain append, position alignment or a single-field join, stay with the ordinary modes rather than moving to SQL for its own sake.
How do I use the array Aggregate produced in an expression?
Just like any other array. Say Aggregate collected name into names: downstream you can write {{ $json.names.join(', ') }} (joins them with a comma and space), {{ $json.names.length }} (how many), {{ $json.names.filter(n => n.startsWith('W')) }} (filter). The full list of these is in Chapter 14, Expressions.