Share This Article
Are you harnessing the full power of n8n for your data automation needs? While n8n shines with its visual workflow builder, sometimes you encounter data manipulation tasks that push the boundaries of standard nodes. A common challenge arises when you need to analyze structured data, like a list of customer interactions or survey responses, and figure out not just individual counts, but how often specific *combinations* of attributes appear. Imagine needing to know how many times each specific ‘Status’ occurred for every unique ‘Audience’ in your dataset. While you might initially try chaining together multiple Filter and Item List nodes, this approach quickly becomes unwieldy. Thankfully, n8n provides a powerful escape hatch for complex logic: the Code node. This post dives deep into using the N8N Code Node: Count Occurrences of Each Item Combination, offering a scalable and elegant solution to this data aggregation challenge.
Understanding the Data Aggregation Challenge: Beyond Simple Counts
In many automation workflows, especially those dealing with CRM data, marketing analytics, or API responses, you receive data as arrays of JSON objects. Each object represents an event, a record, or an item with several properties (keys). A frequent requirement is to summarize this data by grouping and counting based on the values of multiple properties.
Let’s consider a practical scenario. Suppose you’re tracking user engagement activities fetched from an API. Your n8n workflow receives data structured like this:
[ { "Status": "Visit Profil", "Audience": "Company_A" }, { "Status": "Visit Profil", "Audience": "Company_A" }, { "Status": "Mail Sent", "Audience": "Company_A" }, { "Status": "Visit Profil", "Audience": "Company_B" }, { "Status": "Form Submitted", "Audience": "Company_C" }, { "Status": "Visit Profil", "Audience": "Company_A" }, { "Status": "Mail Sent", "Audience": "Company_B" }]
Simply counting the total number of “Visit Profil” statuses isn’t enough. You need a more granular view. You want to understand the interaction patterns *per audience*. Specifically, you want to answer questions like:
- How many times did someone from “Company_A” perform the “Visit Profil” action?
- How many times was a “Mail Sent” to someone associated with “Company_A”?
- How many “Visit Profil” actions are linked to “Company_B”?
The desired outcome isn’t just a flat list of counts; it’s a structured summary, perhaps grouped by Audience, showing the counts for each Status within that Audience:
Conceptual Desired Output:
Company_A:
Visit Profil: 3
Mail Sent: 1
Company_B:
Visit Profil: 1
Mail Sent: 1
Company_C:
Form Submitted: 1
This task involves grouping by one field (‘Audience’) and then counting occurrences of another field (‘Status’) within each group. This is where standard n8n nodes can start to feel limiting, especially if the number of Audiences or Statuses is large or unknown beforehand.
The Initial Approach: Using Standard Nodes (and Why It Falls Short)
When first faced with this challenge in n8n, your instinct might be to use the visual nodes you’re familiar with. It is possible to achieve this kind of counting using a combination of nodes, but it often leads to complex and difficult-to-maintain workflows.
Here’s how you might attempt it using standard nodes:
- Filter/IF Nodes: The first step involves isolating data for a specific combination. You’d start by filtering the incoming items based on one criterion, say,
Audience == "Company_A"
. You could use an IF node or a Filter node for this. - Item Lists Node: After filtering for “Company_A”, you pass these items to an Item Lists node. You’d use the “Aggregate Items” operation. In the “Input Field Name” setting, you would specify the second field you want to count, which is ‘Status’ in our example. This node groups the items by the distinct values found in the ‘Status’ field for the already filtered “Company_A” items.
- Set Node: The Item Lists node outputs items where each item represents a unique ‘Status’ found for “Company_A”, along with the original items that matched that status nested inside. To get the actual count, you add a Set node. In the Set node, you create a new field (e.g., ‘count’). For its value, you use an expression. Within the expression editor, you need to find the aggregated field created by the Item Lists node (it usually has a slightly different icon indicating it’s an array). You then click the grey bubble next to it and select the
length
property. This gives you the count of items for that specific Status within the “Company_A” group. - Repeat for Every Combination: Here’s the major drawback. The process described above only counts statuses for “Company_A”. To get the counts for “Company_B”, “Company_C”, and any other audience, you would need to duplicate this entire sequence of Filter -> Item Lists -> Set nodes for each unique Audience. If you also needed to handle different Statuses dynamically, the branching logic would become incredibly complex, perhaps requiring NoOp nodes to merge paths back together.
Why this approach isn’t scalable:
- Manual Setup: You need to manually configure a branch for every single ‘Audience’ (or the primary grouping criterion). If you have dozens or hundreds of audiences, this is tedious and error-prone.
- Static: The workflow only works for the Audiences you explicitly configure branches for. If a new Audience appears in your data (“Company_D”), it won’t be processed unless you manually update the workflow by adding another branch.
- Complexity: The visual workflow becomes cluttered and hard to follow with numerous parallel branches. Debugging becomes a nightmare.
- Performance: While n8n is efficient, processing data through many sequential filtering and aggregation nodes might be less performant than a single, optimized code block for large datasets.
This node-based method works for very simple cases with a fixed, small number of combinations. However, for dynamic data where you don’t know all possible combinations beforehand, or when dealing with significant variety, it quickly breaks down. This is precisely where the Code node becomes invaluable.
Introducing the Scalable Hero: The N8N Code Node
Enter the Code node – your gateway to unlocking advanced data manipulation and custom logic directly within your n8n workflows. While n8n’s strength lies in its visual, node-based interface, the Code node provides the flexibility of JavaScript programming when you need to perform operations that are cumbersome or impossible with standard nodes alone.
Think of the Code node as a powerful multi-tool. It receives data from preceding nodes, allows you to write custom JavaScript code to process that data, and then outputs the results to be used by subsequent nodes. This is perfect for tasks like:
- Complex data transformations and restructuring.
- Implementing custom business logic.
- Interacting with APIs in unique ways.
- Performing advanced calculations and aggregations – like counting item combinations!
For our specific problem – dynamically counting occurrences of each ‘Status’ for every ‘Audience’ – the Code node offers a significantly more elegant, scalable, and maintainable solution compared to the multi-branch node approach.
Deep Dive: The N8N Code Node Solution for Counting Combinations
Let’s build the scalable solution using the Code node. The core idea is to write JavaScript code that dynamically identifies all unique Audiences and Statuses, then iterates through them to count the matches.
The Input Data
The Code node will receive the array of JSON objects directly from the previous node in your workflow. We’ll use our example data again:
[ { "Status": "Visit Profil", "Audience": "Company_A" }, { "Status": "Visit Profil", "Audience": "Company_A" }, { "Status": "Mail Sent", "Audience": "Company_A" }, { "Status": "Visit Profil", "Audience": "Company_B" }]
Inside the Code node, this input data is typically accessible via the $input
variable (using the modern syntax) or items
(older syntax). We’ll use $input.all()
which provides an array representing all input items.
The JavaScript Logic
Here’s a breakdown of the steps our JavaScript code will perform:
- Identify Unique Values: First, we need to know all the unique ‘Audience’ values and all the unique ‘Status’ values present in the entire input dataset. We can’t hardcode them because new ones might appear later.
- Prepare Results Structure: We’ll initialize an empty array (let’s call it
results
) to store our final summary objects. - Loop Through Unique Audiences: We’ll iterate through each unique ‘Audience’ identified in step 1.
- Create Audience Result Object: For each ‘Audience’, we’ll create a new JSON object. This object will initially contain the audience name itself (e.g.,
{ "audience": "Company_A" }
). - Loop Through Unique Statuses (within Audience loop): Inside the Audience loop, we’ll iterate through each unique ‘Status’ identified in step 1.
- Filter and Count: For the current ‘Audience’ and current ‘Status’, we will filter the original input array to find only those items that match both the current Audience AND the current Status. We then get the
length
of this filtered array, which gives us the count for that specific combination. - Add Status Count to Result Object: If the count is greater than zero (meaning this Status occurred for this Audience), we add the ‘Status’ as a key to the current Audience’s result object, with its count as the value (e.g., adding
"Visit Profil": 2
to the “Company_A” object). - Add Audience Object to Final Results: Once we’ve looped through all Statuses for the current Audience, we add the now complete result object (e.g.,
{ "audience": "Company_A", "Visit Profil": 2, "Mail Sent": 1 }
) to our mainresults
array. - Return Results: After iterating through all unique Audiences, the Code node returns the final
results
array. This array will contain one object for each Audience, summarizing the counts of each Status found for that Audience.
The Code Snippet
Here is the JavaScript code you would place inside the n8n Code node. This uses the modern $input
variable syntax:
const allItems = $input.all(); // Get all input items once for efficiency// Step 1: Identify Unique Values// Use Set for efficient unique value extractionconst unique_audiences = [...new Set(allItems.map(e => e.json.Audience))];const unique_statuses = [...new Set(allItems.map(e => e.json.Status))];// Step 2: Prepare Results Structurelet results = [];// Step 3: Loop Through Unique Audiencesunique_audiences.forEach(audience => { // Step 4: Create Audience Result Object let result = { json: { audience: audience // Use a clear key like 'audience' for the group identifier } }; // Step 5: Loop Through Unique Statuses unique_statuses.forEach(status => { // Step 6: Filter and Count matching items // Filter the original items directly const count = allItems.filter(e => e.json.Audience === audience && e.json.Status === status ).length; // Get the count (length of the filtered array) // Step 7: Add Status Count to Result Object (Optional: only if count > 0) // This check prevents adding statuses with a count of 0, making the output cleaner. if (count > 0) { result.json[status] = count; // Dynamically add the status as a key } }); // End of status loop // Step 8: Add Audience Object to Final Results // Only add if the audience actually had any statuses counted (optional enhancement) // Check if more keys than just 'audience' were added if (Object.keys(result.json).length > 1) { results.push(result); } }); // End of audience loop// Step 9: Return Resultsreturn results; // This array is the output of the Code node
Expected Output from Code Node
Given the example input data, the Code node running the script above would output the following array:
[ { "audience": "Company_A", "Visit Profil": 2, "Mail Sent": 1 }, { "audience": "Company_B", "Visit Profil": 1 }]
Notice how this output is structured exactly as we desired: an array where each object represents an Audience and contains the counts for the Statuses associated with it. This output can then be easily used by subsequent n8n nodes for reporting, database storage, sending notifications, or further processing.
Breaking Down the Code: Understanding the JavaScript
Let’s dissect the key parts of the JavaScript snippet to understand how it achieves the dynamic counting:
const allItems = $input.all();
This line retrieves all items passed into the Code node and stores them in the allItems
variable. Accessing $input.all()
once at the beginning is generally more efficient than calling it repeatedly inside loops.
const unique_audiences = [...new Set(allItems.map(e => e.json.Audience))];
This is a concise way to get unique values.
allItems.map(e => e.json.Audience)
: This iterates through every item (e
) in theallItems
array and extracts just the value of theAudience
property from its JSON data (e.json
). It creates a new array containing only the Audience values, including duplicates (e.g.,["Company_A", "Company_A", "Company_A", "Company_B"]
).new Set(...)
: A JavaScriptSet
is a collection of unique values. When you create a Set from an array containing duplicates, the duplicates are automatically removed. So,new Set(["Company_A", "Company_A", ...])
becomes a Set containing{"Company_A", "Company_B"}
.[... ]
: The spread syntax (...
) converts the Set back into an array. So,unique_audiences
becomes["Company_A", "Company_B"]
.
The same logic applies to finding unique_statuses
.
let results = [];
Initializes an empty array where we will store the final output objects, one for each audience.
unique_audiences.forEach(audience => { ... });
This starts a loop that will execute the code inside the curly braces once for each element in the unique_audiences
array. In each iteration, the current audience value (e.g., “Company_A”, then “Company_B”) is available in the audience
variable.
let result = { json: { audience: audience } };
Inside the audience loop, this creates the basic structure for the output object for the current audience. We nest the data inside a json
key, which is standard practice for n8n Code node outputs to ensure they are correctly interpreted by subsequent nodes.
unique_statuses.forEach(status => { ... });
Nested inside the audience loop, this loop iterates through every unique status found in the data (e.g., “Visit Profil”, “Mail Sent”).
const count = allItems.filter(e => e.json.Audience === audience && e.json.Status === status).length;
This is the core counting logic:
allItems.filter(...)
: This method creates a new array containing only the items fromallItems
that satisfy the condition inside the parentheses.e => e.json.Audience === audience && e.json.Status === status
: This is the filtering condition. For each iteme
, it checks if itsAudience
property strictly equals (===
) the currentaudience
from the outer loop AND (&&
) if itsStatus
property strictly equals the currentstatus
from the inner loop..length
: After filtering, we get thelength
(the number of items) of the resulting array. This gives us the exact count of items matching the specific Audience/Status combination.
if (count > 0) { result.json[status] = count; }
This checks if the calculated count
is greater than zero. If it is, it adds a new property to the result.json
object. Crucially, the *name* of the property is the value of the status
variable (e.g., “Visit Profil”), and the *value* of the property is the calculated count
. This is how we dynamically build the structure like "Visit Profil": 2
.
if (Object.keys(result.json).length > 1) { results.push(result); }
This is an optional enhancement added after the inner status loop completes. It checks if any statuses were actually added to the `result.json` object (i.e., if the object has more keys than just the initial ‘audience’ key). If counts were found, the completed `result` object for the current audience is added (push
ed) to the main results
array.
return results;
Finally, after the outer loop finishes iterating through all audiences, the function returns the results
array, which becomes the output data of the Code node.
Why This Approach Wins: Scalability, Flexibility, Clarity
Using the Code node with this JavaScript logic provides several significant advantages over the node-based branching method:
- Scalability: This is the primary benefit. The code works regardless of how many unique Audiences or Statuses exist in the data. Whether you have 3 combinations or 3,000, the code dynamically identifies them and counts them correctly without any changes to the workflow structure.
- Dynamic Handling: If new Audience values (like “Company_D”) or new Status values appear in your input data tomorrow, the code will automatically include them in the count without requiring manual workflow updates.
- Simplicity & Clarity: While it involves code, the overall workflow becomes much simpler. Instead of a sprawling diagram of dozens of nodes and branches, you have a single, well-defined Code node responsible for the complex aggregation. This makes the workflow easier to understand, debug, and maintain.
- Efficiency: For large datasets, processing the logic within a single JavaScript execution environment can often be more performant than passing data through multiple distinct n8n nodes, each potentially involving data serialization/deserialization steps.
- Flexibility: The Code node opens the door to further customizations. You could easily adapt the script to count combinations of three or more fields, handle missing data gracefully, perform calculations on the counts, or restructure the output in different ways.
The N8N Code node transforms complex, dynamic data aggregation from a potential workflow nightmare into a manageable and efficient task.
Automation Expert
Beyond the Basics: Expanding the Concept
The provided script is a solid foundation for counting two-dimensional combinations. You can adapt this core logic for even more sophisticated scenarios:
- Counting Three or More Combinations: Need to count Status per Audience per Region? You would identify unique Regions, add another nested
forEach
loop for regions, and update the filter condition to match all three criteria (e.json.Audience === audience && e.json.Status === status && e.json.Region === region
). The output structure would also need adjustment, perhaps nesting the status counts within region objects. - Handling Missing or Null Values: The current script assumes ‘Audience’ and ‘Status’ always exist. You might add checks within the
map
orfilter
functions to handle items where these properties might be missing or null, perhaps grouping them under a specific key like “Undefined”. - Case-Insensitive Counting: If “Company_A” and “company_a” should be treated as the same, you could convert the values to lowercase (using
.toLowerCase()
) when extracting unique values and during the filtering comparison. - Calculating Percentages or Ratios: After getting the counts, you could add more code within the loops or after them to calculate percentages (e.g., what percentage of “Company_A” interactions were “Visit Profil”?).
- Different Output Formats: Instead of one object per audience, you might want a flat list where each row represents a unique combination and its count (e.g.,
{ audience: "Company_A", status: "Visit Profil", count: 2 }
). You can easily modify how theresult
object is structured and added to theresults
array.
The key is understanding the core pattern: identify unique keys, loop through them, filter the original data based on the current combination of keys, and aggregate (in this case, count using .length
).
Related Reading
Learning Resources for N8N and JavaScript
Mastering the n8n Code node often involves getting comfortable with JavaScript fundamentals and understanding how n8n handles data within the node. If you’re looking to deepen your skills, here are some excellent resources:
- Free Code Camp: Offers comprehensive, free courses on JavaScript (and many other web technologies) in a structured, written format. Great for building foundational knowledge from scratch.
- 30 Seconds of Code: A curated collection of short JavaScript code snippets for various common tasks. Excellent for learning practical patterns and concise coding techniques you can adapt for the Code node.
- AskJarvis.io: An AI-powered tool specifically designed to help explain code syntax and logic. If you encounter a JavaScript snippet (like the one in this post) and need clarification on specific parts, AskJarvis can often provide helpful explanations.
Additionally, the official n8n documentation and community forums are invaluable resources for specific n8n questions and examples related to the Code node and data handling.
Conclusion: Mastering Dynamic Data Aggregation in N8N
Counting occurrences of item combinations is a common yet potentially tricky task in data processing and workflow automation. While n8n’s standard nodes offer building blocks, they can lead to complex and unscalable solutions when dealing with dynamic or numerous combinations. As we’ve explored, the N8N Code Node: Count Occurrences of Each Item Combination provides a far superior approach.
By leveraging the power of JavaScript within the Code node, you can create workflows that are:
- Dynamic: Automatically adapting to new categories or values in your data.
- Scalable: Handling large numbers of combinations without exponential increases in workflow complexity.
- Maintainable: Consolidating complex logic into a single, manageable code block.
- Efficient: Often providing better performance for complex aggregation tasks.
The key takeaway is recognizing when a task’s complexity, especially involving dynamic grouping and aggregation across multiple fields, warrants stepping beyond the standard visual nodes and embracing the flexibility of the Code node. While it requires some familiarity with JavaScript, the investment pays off immensely in building robust, scalable, and elegant automation solutions with n8n. Don’t shy away from the Code node; view it as a powerful tool in your automation arsenal, ready to tackle the challenges that standard nodes find difficult. Start experimenting, leverage the learning resources available, and unlock a new level of data manipulation power in your n8n workflows.