Learn how to use the Code node in n8n to run custom JavaScript for data transformation, API calls, error handling, and advanced workflow logic.
Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
The Code node in n8n allows you to execute custom JavaScript code within your workflow, enabling complex data transformations, API integrations, and custom logic that might not be possible with standard nodes. This powerful feature lets you manipulate data, make HTTP requests, or implement any JavaScript functionality to enhance your workflow's capabilities.
Step 1: Understanding the Code Node Basics
The Code node is one of the most versatile nodes in n8n. It allows you to write and execute custom JavaScript code directly within your workflow. Before diving into practical examples, let's understand its key components:
Step 2: Adding a Code Node to Your Workflow
To add a Code node to your workflow, follow these steps:
Once added, you'll see the Code node with its configuration panel open. This is where you'll write your JavaScript code.
Step 3: Understanding the Default Code Template
When you add a Code node, it comes with a default code template:
// Code here will run only once, no matter how many input items there are
// Loop over inputs and add a new field called 'myNewField' to the JSON of each one
return items.map(item => {
item.json.myNewField = 1;
return item;
});
This template demonstrates a common pattern:
Step 4: Writing Your First Code Node Script
Let's write a simple script that manipulates data. This example will:
// Function to convert all string values in an object to uppercase
function convertStringsToUpperCase(obj) {
const result = {};
for (const key in obj) {
if (typeof obj[key] === 'string') {
result[key] = obj[key].toUpperCase();
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
result[key] = convertStringsToUpperCase(obj[key]);
} else {
result[key] = obj[key];
}
}
return result;
}
// Process each item
return items.map(item => {
item.json = convertStringsToUpperCase(item.json);
return item;
});
This code:
Step 5: Understanding the Input Data Structure
To effectively use the Code node, you need to understand how data is structured in n8n. Each item passed to the Code node has this structure:
{
json: {
// The actual data
},
binary: {
// Any binary data (files, images, etc.)
},
pairedItem: {
// Information about the original item this was paired with
}
}
Most of the time, you'll work with the json
property, which contains the actual data from previous nodes.
To debug and understand the incoming data, you can add a console.log statement:
// Log the structure of the first item
console.log('First item structure:', JSON.stringify(items[0], null, 2));
// Process items as before
return items.map(item => {
item.json.myNewField = 1;
return item;
});
You can view these logs in the execution details after running the workflow.
Step 6: Processing Each Item Individually
The Code node has two execution modes:
items
array.To switch to the "Run Once for Each Item" mode:
When using this mode, your code needs to be adjusted slightly:
// In "Run Once for Each Item" mode, we work with a single item
// No need for .map() as we're only processing one item
// Add a new field to the current item
item.json.processedIndividually = true;
// Add a timestamp
item.json.processedAt = new Date().toISOString();
// Simply return the modified item
return item;
Step 7: Working with Binary Data
The Code node can also manipulate binary data (files, images, etc.). Here's an example that checks if there's binary data and adds information about it:
return items.map(item => {
// Check if the item has binary data
if (item.binary) {
// Create a summary of binary data
const binarySummary = {};
for (const dataKey in item.binary) {
binarySummary[dataKey] = {
mimeType: item.binary[dataKey].mimeType,
fileSize: item.binary[dataKey].fileSize,
fileName: item.binary[dataKey].fileName
};
}
// Add the summary to the JSON data
item.json.binarySummary = binarySummary;
} else {
item.json.hasBinaryData = false;
}
return item;
});
To fully process binary data, you may need to use the Buffer API and other Node.js functionalities, which are available in the Code node.
Step 8: Making HTTP Requests from the Code Node
The Code node allows you to make HTTP requests using the built-in $http
object. This is useful for integrating with external APIs:
// Make an HTTP request to get weather data
// Example using async/await with the $http object
// This is necessary because we're using async/await
return new Promise(async (resolve, reject) => {
try {
const results = [];
for (const item of items) {
// Extract city from the input
const city = item.json.city || 'London';
// Make API request
const response = await $http.get({
url: `https://api.openweathermap.org/data/2.5/weather`,
params: {
q: city,
appid: 'your_api_key\_here', // Replace with your actual API key
units: 'metric'
}
});
// Add the weather data to the item
item.json.weather = response.data;
results.push(item);
}
resolve(results);
} catch (error) {
// Handle errors
console.error('Error in HTTP request:', error);
reject(error);
}
});
Note: When using async/await or other asynchronous operations, you need to wrap your code in a Promise and use resolve/reject to return the final data.
Step 9: Using Environment Variables
For security and flexibility, you can use environment variables in your Code node:
// Access environment variables
const apiKey = $env.API\_KEY;
const baseUrl = $env.API_BASE_URL || 'https://api.example.com';
return items.map(item => {
// Use the environment variables in your logic
item.json.apiDetails = {
baseUrl: baseUrl,
hasApiKey: !!apiKey
};
return item;
});
To set environment variables in n8n:
Step 10: Adding Conditional Logic
The Code node is excellent for implementing complex conditional logic:
return items.map(item => {
// Categorize items based on their value
if (item.json.amount > 1000) {
item.json.category = 'high-value';
item.json.needsApproval = true;
} else if (item.json.amount > 500) {
item.json.category = 'medium-value';
item.json.needsApproval = item.json.isExpense === true;
} else {
item.json.category = 'low-value';
item.json.needsApproval = false;
}
// Add processing date
item.json.processedDate = new Date().toISOString();
return item;
});
This example categorizes items based on their amount and adds processing metadata.
Step 11: Filtering and Transforming Data
You can use the Code node to filter items or transform data structures:
// Filter out items with status "rejected" and transform the remaining data
const filteredItems = items.filter(item => item.json.status !== 'rejected');
return filteredItems.map(item => {
// Create a completely new data structure
return {
json: {
id: item.json.id,
fullName: `${item.json.firstName} ${item.json.lastName}`,
contactInfo: {
email: item.json.email,
phone: item.json.phone
},
// Calculate age if birthDate is available
age: item.json.birthDate
? Math.floor((new Date() - new Date(item.json.birthDate)) / (365.25 _ 24 _ 60 _ 60 _ 1000))
: null,
// Add any other transformations
isActive: item.json.status === 'active',
lastUpdated: new Date().toISOString()
}
};
});
This example:
Step 12: Working with Dates and Time
Date manipulation is a common task in workflows. Here's how to work with dates in the Code node:
return items.map(item => {
// Get the current date
const now = new Date();
// Format date as YYYY-MM-DD
const formatDate = (date) => {
return date.toISOString().split('T')[0];
};
// Add various date information
item.json.currentDate = formatDate(now);
// Add 7 days to the current date
const nextWeek = new Date(now);
nextWeek.setDate(now.getDate() + 7);
item.json.nextWeekDate = formatDate(nextWeek);
// Calculate difference in days if there's a dueDate
if (item.json.dueDate) {
const dueDate = new Date(item.json.dueDate);
const diffTime = dueDate - now;
const diffDays = Math.ceil(diffTime / (1000 _ 60 _ 60 \* 24));
item.json.daysUntilDue = diffDays;
item.json.isOverdue = diffDays < 0;
}
return item;
});
This example:
Step 13: Error Handling in the Code Node
Proper error handling is crucial for robust workflows. Here's how to implement it in the Code node:
// Wrap the entire processing in a try-catch block
try {
return items.map(item => {
try {
// Attempt to process the item
// Example: Parse a JSON string
if (item.json.jsonData && typeof item.json.jsonData === 'string') {
try {
item.json.parsedData = JSON.parse(item.json.jsonData);
} catch (parseError) {
// Handle JSON parsing error
item.json.parsedData = null;
item.json.parseError = 'Invalid JSON format';
console.log(`Error parsing JSON for item ${item.json.id}: ${parseError.message}`);
}
}
// Example: Perform a calculation that might fail
if (item.json.x !== undefined && item.json.y !== undefined) {
if (item.json.y === 0 && item.json.operation === 'divide') {
item.json.calculationError = 'Division by zero';
item.json.result = null;
} else {
// Perform the calculation
switch (item.json.operation) {
case 'add':
item.json.result = item.json.x + item.json.y;
break;
case 'subtract':
item.json.result = item.json.x - item.json.y;
break;
case 'multiply':
item.json.result = item.json.x \* item.json.y;
break;
case 'divide':
item.json.result = item.json.x / item.json.y;
break;
default:
item.json.calculationError = 'Unknown operation';
item.json.result = null;
}
}
}
return item;
} catch (itemError) {
// Handle errors for individual items
console.error(`Error processing item ${item.json.id || 'unknown'}:`, itemError);
// You can choose to return the original item with an error flag
item.json.processingError = itemError.message;
return item;
// Or you could return a completely new error item
// return {
// json: {
// error: true,
// originalItem: item.json,
// errorMessage: itemError.message
// }
// };
}
});
} catch (generalError) {
// Handle catastrophic errors that affect the entire execution
console.error('Critical error in Code node:', generalError);
// Return an error message that will be passed to the next node
return [{
json: {
error: true,
errorType: 'execution\_failure',
errorMessage: generalError.message,
timestamp: new Date().toISOString()
}
}];
}
This example demonstrates:
Step 14: Working with Arrays and Advanced Data Manipulation
The Code node is perfect for complex data manipulations. Here's an example that performs several array operations:
return items.map(item => {
// Create a copy of the original data
item.json.original = JSON.parse(JSON.stringify(item.json));
// Example 1: Flatten a nested array
if (Array.isArray(item.json.nestedArray)) {
item.json.flattenedArray = item.json.nestedArray.flat(Infinity);
}
// Example 2: Group array items by a property
if (Array.isArray(item.json.people)) {
const groupedByAge = {};
item.json.people.forEach(person => {
const ageGroup = Math.floor(person.age / 10) \* 10; // Group by decade
const key = `${ageGroup}s`; // e.g., "20s", "30s", etc.
if (!groupedByAge[key]) {
groupedByAge[key] = [];
}
groupedByAge[key].push(person);
});
item.json.peopleByAgeGroup = groupedByAge;
}
// Example 3: Calculate statistics for numeric arrays
if (Array.isArray(item.json.numbers)) {
const numbers = item.json.numbers.filter(n => typeof n === 'number');
if (numbers.length > 0) {
const sum = numbers.reduce((total, n) => total + n, 0);
const avg = sum / numbers.length;
const min = Math.min(...numbers);
const max = Math.max(...numbers);
item.json.numberStats = {
sum,
average: avg,
min,
max,
count: numbers.length
};
}
}
// Example 4: Find duplicates in an array
if (Array.isArray(item.json.values)) {
const valueCount = {};
item.json.values.forEach(val => {
valueCount[val] = (valueCount[val] || 0) + 1;
});
item.json.duplicates = Object.entries(valueCount)
.filter(([\_, count]) => count > 1)
.map(([value, count]) => ({ value, count }));
}
return item;
});
This example demonstrates several techniques:
Step 15: Using External Libraries with the Code Node
The Code node in n8n has access to several built-in libraries. Here's how to use them:
// Using the built-in moment.js library for date manipulation
const moment = require('moment');
// Using lodash for utility functions
const \_ = require('lodash');
return items.map(item => {
// Using moment.js to format dates
if (item.json.timestamp) {
item.json.formattedDate = moment(item.json.timestamp).format('MMMM Do YYYY, h:mm:ss a');
item.json.relativeTime = moment(item.json.timestamp).fromNow();
}
// Using lodash to manipulate data
if (item.json.data) {
// Deep merge objects
item.json.mergedData = \_.merge({}, item.json.defaultValues || {}, item.json.data);
// Group by a property
if (Array.isArray(item.json.records)) {
item.json.groupedRecords = \_.groupBy(item.json.records, 'category');
}
// Pick specific properties
if (item.json.user) {
item.json.userSummary = \_.pick(item.json.user, ['id', 'name', 'email']);
}
}
return item;
});
Built-in libraries include:
Step 16: Generating Dynamic Outputs
Sometimes you need to create entirely new items or change the number of output items:
// Example: Exploding an array into multiple items
let resultItems = [];
items.forEach(item => {
// Check if the item has an array we want to explode
if (Array.isArray(item.json.elements)) {
// Create a new item for each element in the array
item.json.elements.forEach(element => {
resultItems.push({
json: {
originalItemId: item.json.id,
element: element,
timestamp: new Date().toISOString()
}
});
});
} else {
// If there's no array to explode, just pass through the original item
resultItems.push(item);
}
});
return resultItems;
This technique is useful when:
Step 17: Implementing Pagination and Batching
For workflows dealing with large datasets, you might need to implement pagination or batching:
// Get pagination details from the incoming data or use defaults
const currentPage = items[0]?.json?.currentPage || 1;
const pageSize = items[0]?.json?.pageSize || 10;
const totalItems = items.length;
const totalPages = Math.ceil(totalItems / pageSize);
// Calculate the slice of items for the current page
const startIndex = (currentPage - 1) \* pageSize;
const endIndex = Math.min(startIndex + pageSize, totalItems);
const pageItems = items.slice(startIndex, endIndex);
// Add pagination metadata to each item
const result = pageItems.map(item => {
item.json.pagination = {
page: currentPage,
pageSize,
totalItems,
totalPages,
hasNextPage: currentPage < totalPages,
hasPrevPage: currentPage > 1
};
return item;
});
// Add navigation links as a separate item if needed
if (items[0]?.json?.includePaginationLinks) {
const baseUrl = items[0]?.json?.baseUrl || 'https://api.example.com/items';
result.push({
json: {
\_type: 'paginationLinks',
current: `${baseUrl}?page=${currentPage}&pageSize=${pageSize}`,
first: `${baseUrl}?page=1&pageSize=${pageSize}`,
last: `${baseUrl}?page=${totalPages}&pageSize=${pageSize}`,
next: currentPage < totalPages ? `${baseUrl}?page=${currentPage + 1}&pageSize=${pageSize}` : null,
prev: currentPage > 1 ? `${baseUrl}?page=${currentPage - 1}&pageSize=${pageSize}` : null
}
});
}
return result;
This code implements:
Step 18: Creating Reusable Functions
For complex workflows, you can create reusable utility functions in your Code node:
// Utility functions
const utils = {
// Format currency values
formatCurrency: (amount, currency = 'USD') => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency
}).format(amount);
},
// Sanitize HTML content
sanitizeHtml: (html) => {
if (!html || typeof html !== 'string') return '';
return html
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
},
// Generate a slug from a string
slugify: (text) => {
return text
.toString()
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/[^\w-]+/g, '')
.replace(/--+/g, '-')
.replace(/^-+/, '')
.replace(/-+$/, '');
},
// Extract domain from email
getDomainFromEmail: (email) => {
if (!email || typeof email !== 'string') return null;
const matches = email.match(/@([^@]+)$/);
return matches ? matches[1] : null;
},
// Check if a date is in the future
isFutureDate: (dateStr) => {
const date = new Date(dateStr);
return date > new Date();
}
};
// Process items using the utility functions
return items.map(item => {
// Apply various transformations using the utility functions
if (item.json.amount !== undefined && item.json.currency) {
item.json.formattedAmount = utils.formatCurrency(item.json.amount, item.json.currency);
}
if (item.json.title) {
item.json.slug = utils.slugify(item.json.title);
}
if (item.json.htmlContent) {
item.json.safeHtml = utils.sanitizeHtml(item.json.htmlContent);
}
if (item.json.email) {
item.json.domain = utils.getDomainFromEmail(item.json.email);
}
if (item.json.dueDate) {
item.json.isInFuture = utils.isFutureDate(item.json.dueDate);
}
return item;
});
Creating a utilities object:
Step 19: Combining Multiple Input Sources
Sometimes your Code node needs to combine data from multiple input sources:
// This example assumes the Code node has two input connections
// First, separate items by their source
const sourceA = [];
const sourceB = [];
items.forEach(item => {
// Check for a marker or infer the source
if (item.json.source === 'A' || item.json.\_sourceNodeName === 'Webhook') {
sourceA.push(item);
} else if (item.json.source === 'B' || item.json.\_sourceNodeName === 'Database') {
sourceB.push(item);
}
});
// Now combine the data as needed
const results = [];
// Example 1: Join items from source A with items from source B based on a key
sourceA.forEach(itemA => {
const matchingItemB = sourceB.find(itemB => itemB.json.id === itemA.json.relatedId);
if (matchingItemB) {
results.push({
json: {
id: itemA.json.id,
name: itemA.json.name,
// Combine data from both sources
details: matchingItemB.json.details,
category: matchingItemB.json.category,
// Add metadata
combinedFrom: ['A', 'B'],
timestamp: new Date().toISOString()
}
});
} else {
// No matching item found, just use source A data
results.push({
json: {
...itemA.json,
combinedFrom: ['A'],
hasMatch: false,
timestamp: new Date().toISOString()
}
});
}
});
// Example 2: Find items in source B that don't have a match in source A
const unmatchedB = sourceB.filter(itemB =>
!sourceA.some(itemA => itemA.json.relatedId === itemB.json.id)
);
unmatchedB.forEach(item => {
results.push({
json: {
...item.json,
combinedFrom: ['B'],
hasMatch: false,
timestamp: new Date().toISOString()
}
});
});
return results;
This approach lets you:
Step 20: Performance Considerations and Best Practices
When working with the Code node, keep these performance considerations and best practices in mind:
// Example demonstrating performance best practices
// 1. Pre-allocate arrays when you know the size
const resultItems = new Array(items.length);
// 2. Use efficient loops
for (let i = 0; i < items.length; i++) {
const item = items[i];
// 3. Use direct property access rather than deep nesting when possible
const id = item.json.id;
const name = item.json.name;
// 4. Avoid repeated computations
const timestamp = new Date().toISOString(); // Calculate once per iteration
// 5. Minimize object creation and copying
const resultJson = {};
// Only copy what you need
resultJson.id = id;
resultJson.name = name;
resultJson.processed = true;
resultJson.timestamp = timestamp;
// 6. For large arrays, consider using array methods like map/filter/reduce
// But for extremely large arrays, traditional for loops may be faster
// 7. Store the result directly in the pre-allocated array
resultItems[i] = { json: resultJson };
}
// 8. Only return what you need
return resultItems;
Additional best practices:
Step 21: Debugging Code Node Execution
Effective debugging is crucial when working with the Code node. Here are some techniques:
// Comprehensive debugging example
// 1. Add verbose logging to trace execution
console.log('Code node execution started');
console.log(`Processing ${items.length} items`);
// 2. Inspect the structure of the first item
if (items.length > 0) {
console.log('First item structure:', JSON.stringify(items[0], null, 2));
}
// 3. Create a debugging helper function
function debugValue(label, value) {
console.log(`DEBUG - ${label}:`, JSON.stringify(value, null, 2));
}
// 4. Use try-catch blocks to find errors
try {
const results = items.map((item, index) => {
try {
debugValue(`Processing item ${index}`, item.json);
// Your processing logic here
const processed = {
id: item.json.id,
name: item.json.name?.toUpperCase() || 'UNNAMED',
timestamp: new Date().toISOString()
};
debugValue(`Processed result for item ${index}`, processed);
return {
json: processed
};
} catch (itemError) {
console.error(`Error processing item ${index}:`, itemError);
debugValue(`Problematic item ${index}`, item.json);
// Return an error object instead of failing
return {
json: {
error: true,
originalItem: item.json,
errorMessage: itemError.message
}
};
}
});
console.log('Code node execution completed successfully');
return results;
} catch (generalError) {
console.error('Fatal error in Code node:', generalError);
// Return a single error item
return [{
json: {
fatalError: true,
errorMessage: generalError.message,
timestamp: new Date().toISOString()
}
}];
}
Debugging tips:
Step 22: Advanced Data Processing Patterns
Let's explore some advanced data processing patterns that can be implemented with the Code node:
// Advanced data processing examples
// 1. Recursive data transformation (e.g., for nested objects or arrays)
function transformNestedStructure(obj, transformFn) {
if (Array.isArray(obj)) {
return obj.map(item => transformNestedStructure(item, transformFn));
} else if (obj !== null && typeof obj === 'object') {
const result = {};
for (const key in obj) {
result[key] = transformNestedStructure(obj[key], transformFn);
}
return result;
} else {
return transformFn(obj);
}
}
// 2. Data reduction/aggregation
function aggregateData(items, groupByFn, aggregateFn) {
const groups = {};
items.forEach(item => {
const key = groupByFn(item);
if (!groups[key]) {
groups[key] = [];
}
groups[key].push(item);
});
const result = {};
for (const key in groups) {
result[key] = aggregateFn(groups[key]);
}
return result;
}
// Example usage of these patterns
return items.map(item => {
// Example 1: Transform all string values in a nested structure to uppercase
if (item.json.nestedData) {
item.json.transformedData = transformNestedStructure(
item.json.nestedData,
value => typeof value === 'string' ? value.toUpperCase() : value
);
}
// Example 2: Aggregate sales data by region
if (Array.isArray(item.json.sales)) {
item.json.salesByRegion = aggregateData(
item.json.sales,
sale => sale.region,
salesInRegion => ({
totalAmount: salesInRegion.reduce((sum, sale) => sum + sale.amount, 0),
count: salesInRegion.length,
averageAmount: salesInRegion.reduce((sum, sale) => sum + sale.amount, 0) / salesInRegion.length,
items: salesInRegion.map(sale => sale.item)
})
);
}
return item;
});
These patterns enable:
Step 23: Integrating with External Systems
The Code node can be used to integrate with external systems through APIs. Here's a comprehensive example:
// Example integrating with multiple external APIs
return new Promise(async (resolve, reject) => {
try {
const results = [];
for (const item of items) {
// Step 1: Enrich data from first API
try {
const userResponse = await $http.get({
url: `https://api.example.com/users/${item.json.userId}`,
headers: {
'Authorization': `Bearer ${$env.API_TOKEN}`
}
});
item.json.userDetails = userResponse.data;
} catch (userError) {
console.error(`Error fetching user data for ${item.json.userId}:`, userError.message);
item.json.userDetails = { error: userError.message };
}
// Step 2: Post data to another system
if (item.json.shouldSync && !item.json.userDetails.error) {
try {
const syncResponse = await $http.post({
url: 'https://another-system.com/api/sync',
headers: {
'Content-Type': 'application/json',
'X-API-Key': $env.SYNC_API_KEY
},
body: {
externalId: item.json.id,
name: item.json.name,
email: item.json.userDetails.email,
department: item.json.userDetails.department,
syncTimestamp: new Date().toISOString()
}
});
item.json.syncResult = {
success: true,
syncId: syncResponse.data.id,
timestamp: syncResponse.data.timestamp
};
} catch (syncError) {
console.error(`Error syncing data for ${item.json.id}:`, syncError.message);
item.json.syncResult = {
success: false,
error: syncError.message,
timestamp: new Date().toISOString()
};
}
}
// Step 3: Check status from a third system
if (item.json.externalIds && Array.isArray(item.json.externalIds)) {
const statusResults = [];
for (const externalId of item.json.externalIds) {
try {
const statusResponse = await $http.get({
url: `https://status-system.com/api/check/${externalId}`,
headers: {
'Authorization': `ApiKey ${$env.STATUS_API_KEY}`
}
});
statusResults.push({
id: externalId,
status: statusResponse.data.status,
lastUpdated: statusResponse.data.lastUpdated,
success: true
});
} catch (statusError) {
statusResults.push({
id: externalId,
success: false,
error: statusError.message
});
}
// Add a small delay to avoid rate limiting
await new Promise(r => setTimeout(r, 100));
}
item.json.statusChecks = statusResults;
}
results.push(item);
}
resolve(results);
} catch (error) {
console.error('Critical error in external integration:', error);
reject(error);
}
});
This example demonstrates:
Step 24: Creating Custom Workflow Control Logic
The Code node can implement custom workflow control logic like conditional branching:
// Example: Implementing conditional branching logic
// This separates items into different "branches" based on conditions
// Initialize containers for different output branches
const highPriorityItems = [];
const mediumPriorityItems = [];
const lowPriorityItems = [];
const errorItems = [];
// Process each item and assign to appropriate branch
items.forEach(item => {
try {
// Implement your branching logic
if (!item.json.status) {
throw new Error('Missing status field');
}
// Route based on priority or other conditions
if (item.json.priority === 'high' || item.json.status === 'urgent') {
// Add branch identifier
item.json.\_branch = 'highPriority';
highPriorityItems.push(item);
}
else if (item.json.priority === 'medium' ||
(item.json.status === 'active' && item.json.daysOverdue > 0)) {
item.json.\_branch = 'mediumPriority';
mediumPriorityItems.push(item);
}
else {
item.json.\_branch = 'lowPriority';
lowPriorityItems.push(item);
}
} catch (error) {
// Handle errors
errorItems.push({
json: {
\_branch: 'error',
originalItem: item.json,
error: error.message,
timestamp: new Date().toISOString()
}
});
}
});
// Combine all items, maintaining the branch order
// (You can change this order to prioritize different branches)
const allItems = [
...highPriorityItems,
...mediumPriorityItems,
...lowPriorityItems,
...errorItems
];
// Add branch position metadata
allItems.forEach((item, index) => {
const branch = item.json.\_branch;
// Add position metadata
item.json.\_index = index;
item.json.\_total = allItems.length;
// Add branch-specific metadata
switch(branch) {
case 'highPriority':
item.json.\_branchIndex = highPriorityItems.indexOf(item);
item.json.\_branchTotal = highPriorityItems.length;
break;
case 'mediumPriority':
item.json.\_branchIndex = mediumPriorityItems.indexOf(item);
item.json.\_branchTotal = mediumPriorityItems.length;
break;
case 'lowPriority':
item.json.\_branchIndex = lowPriorityItems.indexOf(item);
item.json.\_branchTotal = lowPriorityItems.length;
break;
case 'error':
item.json.\_branchIndex = errorItems.indexOf(item);
item.json.\_branchTotal = errorItems.length;
break;
}
});
return allItems;
This approach enables:
You can use the IF node after the Code node to route items based on the _branch
property.
Step 25: Creating Reusable Code Templates
For frequently used patterns, create reusable code templates that you can copy into new Code nodes:
/\*\*
- n8n Code Node Template
-
- Purpose: Data transformation and validation template
- Features:
- - Input validation
- - Type conversion
- - Data cleaning
- - Schema validation
- - Error handling
\*/
// Helper functions for validation and transformation
const helpers = {
// Type checking and conversion
isString: value => typeof value === 'string',
isNumber: value => typeof value === 'number' && !isNaN(value),
isBoolean: value => typeof value === 'boolean',
isObject: value => value !== null && typeof value === 'object' && !Array.isArray(value),
isArray: value => Array.isArray(value),
toNumber: (value, defaultValue = 0) => {
const num = Number(value);
return isNaN(num) ? defaultValue : num;
},
toString: (value, defaultValue = '') => {
if (value === null || value === undefined) return defaultValue;
return String(value);
},
toBoolean: (value) => {
if (typeof value === 'boolean') return value;
if (typeof value === 'string') {
const lowered = value.toLowerCase();
return lowered === 'true' || lowered === 'yes' || lowered === '1';
}
return Boolean(value);
},
// Data cleaning
trim: (value) => {
if (typeof value !== 'string') return value;
return value.trim();
},
removeNulls: (obj) => {
const result = {};
for (const key in obj) {
if (obj[key] !== null && obj[key] !== undefined) {
result[key] = obj[key];
}
}
return result;
},
// Validation
validateEmail: (email) => {
if (typeof email !== 'string') return false;
const re = /^[^\s@]+@[^\s@]+.[^\s@]+$/;
return re.test(email);
},
validateSchema: (data, schema) => {
const errors = [];
for (const field in schema) {
const rules = schema[field];
const value = data[field];
// Check required fields
if (rules.required && (value === undefined || value === null)) {
errors.push(`Field '${field}' is required`);
continue;
}
// Skip further validation if field is not present and not required
if (value === undefined || value === null) continue;
// Type validation
if (rules.type) {
let typeValid = false;
switch(rules.type) {
case 'string':
typeValid = typeof value === 'string';
break;
case 'number':
typeValid = typeof value === 'number' && !isNaN(value);
break;
case 'boolean':
typeValid = typeof value === 'boolean';
break;
case 'object':
typeValid = typeof value === 'object' && value !== null && !Array.isArray(value);
break;
case 'array':
typeValid = Array.isArray(value);
break;
}
if (!typeValid) {
errors.push(`Field '${field}' should be of type ${rules.type}`);
}
}
// Custom validation
if (rules.validate && typeof rules.validate === 'function') {
const isValid = rules.validate(value);
if (!isValid) {
errors.push(`Field '${field}' failed validation: ${rules.message || 'Invalid value'}`);
}
}
}
return {
valid: errors.length === 0,
errors
};
}
};
// Main processing function
function processItem(item) {
try {
// Define your expected schema
const schema = {
name: { type: 'string', required: true },
email: {
type: 'string',
required: true,
validate: helpers.validateEmail,
message: 'Invalid email format'
},
age: { type: 'number' },
isActive: { type: 'boolean' }
};
// Clean and prepare data
const cleanData = helpers.removeNulls(item.json);
// Validate against schema
const validation = helpers.validateSchema(cleanData, schema);
if (!validation.valid) {
return {
json: {
valid: false,
originalData: item.json,
errors: validation.errors,
processed: false
}
};
}
// Process and transform the data
const processedData = {
name: helpers.trim(cleanData.name),
email: helpers.trim(cleanData.email).toLowerCase(),
age: helpers.toNumber(cleanData.age),
isActive: helpers.toBoolean(cleanData.isActive),
// Add derived or additional fields
domain: cleanData.email.split('@')[1],
processed: true,
processedAt: new Date().toISOString()
};
return {
json: processedData
};
} catch (error) {
console.error('Error processing item:', error);
return {
json: {
error: true,
message: error.message,
originalData: item.json,
processed: false
}
};
}
}
// Process all items
return items.map(processItem);
Benefits of creating templates:
Consider creating templates for:
Conclusion
The Code node is one of the most powerful features in n8n, enabling you to extend your workflows with custom JavaScript code. Throughout this guide, we've covered everything from basic usage to advanced techniques:
By mastering the Code node, you can overcome limitations of standard nodes and create truly customized workflows tailored to your specific needs. Remember to start simple, test thoroughly, and gradually build up complexity as you become more comfortable with the Code node's capabilities.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.