n8n Data Transform: Map, Reduce, Filter & Aggregate Patterns

I use n8n’s data transformation nodes because raw API responses are almost never in the shape you need. You get nested objects, arrays within arrays, missing fields, and inconsistent formats. The Code node and Expression node turn chaos into clean data. Here’s how I do it.

Most n8n workflows fail at the data transformation step. Not because the integrations don’t work, but because nobody plans for the messy middle ground between source and destination. I learned this the hard way when a Stripe webhook delivered a nested object that broke my entire downstream workflow.

This guide covers the four core patterns I use every day: Map, Reduce, Filter, and Aggregate. These aren’t theoretical concepts. They’re practical techniques for transforming data in n8n.

Map: Transform Each Item

Mapping means applying a transformation to every item in a list. In n8n, this happens naturally because nodes process items individually. But sometimes you need to transform nested structures or compute derived values. That’s where the Code node shines.

Here’s a common scenario. An API returns user objects with camelCase keys. Your destination database expects snake_case. The Code node handles the conversion:

`javascript
// Map: Convert camelCase to snake_case
const data = $input.all();
return data.map(item => {
const snake = {};
for (const [key, value] of Object.entries(item.json)) {
const snakeKey = key.replace(/([A-Z])/g, ‘_$1’).toLowerCase().replace(/^_/, ”);
snake[snakeKey] = value;
}
return { json: snake };
});
`

This maps over every incoming item, converts each key, and returns a properly formatted object. The Set node can handle simple field mapping, but complex transformations like this require code.

For date formatting, I use a map pattern too:

`javascript
// Map: Normalize date formats
return $input.all().map(item => {
const date = new Date(item.json.created_at);
return {
json: {
…item.json,
created_date: date.toISOString().split(‘T’)[0],
created_month: date.toLocaleString(‘default’, { month: ‘long’ }),
created_year: date.getFullYear()
}
};
});
`

Each item gets three new date-derived fields. The original data stays intact. Downstream nodes can use whichever format they need.

The Expression node handles simple mappings without code. Use the @ operator to access parent data:

`
={{ $json.items.[0].price * $json.items.[0].quantity }}
`

This computes a total inline. No Code node needed for basic arithmetic. Use expressions for simple transformations. Use Code nodes for complex logic.

Filter: Remove Unwanted Items

Filtering means keeping only the items that meet certain criteria. n8n has a dedicated Filter node, but the Code node gives you more control. I use both depending on complexity.

The Filter node works well for straightforward conditions. Set the expression to ={{ $json.status == "active" }} and only active items pass through. Simple, visual, easy to debug.

For complex filtering, I write code:

`javascript
// Filter: Keep items matching multiple conditions
return $input.all().filter(item => {
const data = item.json;
// Must have a price, be in stock, and be active
if (!data.price || data.price <= 0) return false; if (data.stock === 0) return false; if (data.status !== 'active') return false; // Must have been created in the last 30 days const thirtyDaysAgo = new Date(); thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); return new Date(data.created_at) >= thirtyDaysAgo;
});
`

This filters products by price, stock, status, and recency. All conditions must pass. Items that fail any check get dropped from the workflow.

Filtering is crucial when dealing with paginated APIs. Some endpoints return metadata alongside data. You only want the data items:

`javascript
// Filter: Extract only data items from mixed response
return $input.all().filter(item => {
return typeof item.json.id === ‘string’ && item.json.hasOwnProperty(‘name’);
});
`

This keeps items that look like actual records (have a string id and a name field) and drops metadata, headers, or error objects.

Reduce: Combine Items into a Summary

Reduction means taking multiple items and combining them into a single result. Think total sales, average rating, or unique categories. In n8n, I use the Code node with a reduce pattern:

`javascript
// Reduce: Calculate summary statistics
const items = $input.all().map(item => item.json);

const total = items.reduce((sum, item) => sum + (item.amount || 0), 0);
const avg = total / items.length;
const max = Math.max(…items.map(i => i.amount || 0));
const min = Math.min(…items.map(i => i.amount || 0));

return [{
json: {
total_sales: total,
average_sale: Math.round(avg * 100) / 100,
max_sale: max,
min_sale: min,
transaction_count: items.length
}
}];
`

This reduces a list of transactions into a single summary object. The original items are gone, replaced by aggregated statistics. Perfect for reporting workflows.

Group-then-reduce is another powerful pattern. Group items by a field, then reduce each group:

`javascript
// Reduce: Group by category and sum amounts
const items = $input.all().map(item => item.json);
const groups = {};

items.forEach(item => {
const category = item.category || ‘uncategorized’;
if (!groups[category]) {
groups[category] = { category, total: 0, count: 0 };
}
groups[category].total += item.amount || 0;
groups[category].count++;
});

return Object.values(groups).map(group => ({ json: group }));
`

This groups transactions by category and calculates totals for each. The output is one summary row per category. Clean, structured, ready for a spreadsheet or database insert.

Aggregate: Complex Multi-Step Transformations

Aggregation combines mapping, filtering, and reducing into a single pipeline. I use this pattern when data needs multiple stages of transformation before it’s ready for the destination.

Here’s a real-world example. Process an order list: filter cancelled orders, map shipping addresses, reduce to totals by region:

`javascript
// Aggregate: Full pipeline in one Code node
const orders = $input.all().map(item => item.json);

// Step 1: Filter – remove cancelled orders
const activeOrders = orders.filter(o => o.status !== ‘cancelled’);

// Step 2: Map – normalize fields
const normalized = activeOrders.map(o => ({
region: o.shipping_country || ‘unknown’,
total: (o.subtotal || 0) + (o.tax || 0) – (o.discount || 0),
items: o.line_items?.length || 0
}));

// Step 3: Reduce – aggregate by region
const byRegion = {};
normalized.forEach(o => {
if (!byRegion[o.region]) {
byRegion[o.region] = { region: o.region, revenue: 0, orders: 0, items: 0 };
}
byRegion[o.region].revenue += o.total;
byRegion[o.region].orders++;
byRegion[o.region].items += o.items;
});

return Object.values(byRegion).map(r => ({ json: r }));
`

One Code node. Three transformation steps. Clean output ready for reporting. This is the kind of aggregation that would normally require three separate nodes and multiple data passes.

For the n8n database nodes, this aggregated data can be inserted directly. One INSERT statement per region instead of hundreds of individual inserts. Much faster, much cleaner.

When to Use Each Pattern

Map when you need to transform individual items consistently. Filter when you need to remove unwanted data. Reduce when you need a single summary from many items. Aggregate when you need multiple transformation steps.

The n8n Code Node guide covers JavaScript and Python execution in more detail. The n8n Expression Node reference explains formula-based transformations.

Action Card: Quick Transform Template

Copy this template for common data transformations:

`javascript
// MAP: Transform each item
return $input.all().map(item => ({
json: {
id: item.json.id,
name: item.json.title.toUpperCase(),
price: item.json.price * 1.1 // add 10% tax
}
}));

// FILTER: Keep matching items
return $input.all().filter(item =>
item.json.status === ‘active’ && item.json.value > 100
);

// REDUCE: Combine into summary
const items = $input.all().map(i => i.json);
return [{
json: {
count: items.length,
total: items.reduce((s, i) => s + (i.value || 0), 0)
}
}];
`

Choose the pattern that matches your data need. Combine them in sequence for complex pipelines.

References

  • n8n Code Node Guide
  • n8n Expression Node Reference
  • n8n Database Nodes
  • JavaScript Array Methods
  • n8n Core Nodes Overview
  • Leave a Reply

    Your email address will not be published. Required fields are marked *