/n8n-tutorials

How to use the Code node in n8n?

Learn how to use the Code node in n8n to run custom JavaScript for data transformation, API calls, error handling, and advanced workflow logic.

Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

Book a free consultation

How to use the Code node in n8n?

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:

  • The Code field: This is where you write your JavaScript code.
  • The items variable: This contains the data from the previous node in your workflow.
  • Execution mode: The Code node can run once for all items ("Run Once for All Items") or once per item ("Run Once for Each Item").
  • Node outputs: Your code must return data that will be passed to the next node in the workflow.

 

Step 2: Adding a Code Node to Your Workflow

 

To add a Code node to your workflow, follow these steps:

  • Open your n8n workflow or create a new one.
  • Click on the "+" button to add a new node.
  • Search for "Code" in the node finder.
  • Select the Code node from the results.

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:

  • It takes the incoming items from previous nodes.
  • It processes each item using the `.map()` function.
  • It adds a new field to each item.
  • It returns the modified items to be passed to the next node.

 

Step 4: Writing Your First Code Node Script

 

Let's write a simple script that manipulates data. This example will:

  1. Take incoming data
  2. Convert any string fields to uppercase
  3. Return the modified data
// 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:

  • Defines a recursive function that converts all string values in an object to uppercase.
  • Processes each input item using this function.
  • Returns the modified items.

 

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:

  1. Run Once for All Items (default): Your code receives all items at once in the items array.
  2. Run Once for Each Item: Your code runs separately for each item, and you work with a single item instead of an array.

To switch to the "Run Once for Each Item" mode:

  • Click on the Code node to open its settings.
  • Find the "Execution Mode" dropdown.
  • Select "Run Once for Each Item".

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:

  • Go to Settings > Environment Variables
  • Add your variables (e.g., API_KEY, API_BASE\_URL)
  • Save the changes

 

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:

  • Filters out rejected items
  • Creates a new data structure for the remaining items
  • Combines fields (firstName + lastName)
  • Reorganizes the data (nesting contactInfo)
  • Performs calculations (age from birthDate)

 

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:

  • Gets the current date
  • Formats dates in a consistent way
  • Calculates future dates
  • Determines the difference between dates
  • Checks if dates are in the past

 

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:

  • Multi-level error handling (for the entire execution and for individual items)
  • Specific error handling for different operations
  • Logging errors for debugging
  • Graceful degradation (continuing despite errors)
  • Error information propagation to downstream nodes

 

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:

  • Creating deep copies of objects
  • Flattening nested arrays
  • Grouping array items by properties
  • Calculating statistics from numerical data
  • Finding duplicates in arrays

 

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:

  • moment.js - For date manipulation
  • lodash - For utility functions
  • crypto - For cryptographic operations
  • xml2js - For XML parsing
  • luxon - Alternative date library

 

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:

  • You need to split items with arrays into multiple individual items
  • You're aggregating or summarizing data and want to reduce the number of items
  • You're generating reports or new data structures

 

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:

  • Pagination logic to divide data into pages
  • Metadata about the current page and total pages
  • Optional navigation links for API-style responses

 

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:

  • Makes your code more organized
  • Promotes code reuse
  • Makes complex transformations easier to understand
  • Simplifies testing and debugging

 

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:

  • Join data from different sources based on related fields
  • Perform operations similar to SQL JOINs (inner, left, right, full)
  • Create comprehensive reports combining multiple data sources
  • Identify matching and non-matching records

 

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:

  • Comment your code to explain complex logic
  • Break down complex operations into smaller, reusable functions
  • Use meaningful variable names for better readability
  • Handle errors gracefully to prevent workflow failures
  • Test with small data samples before processing large datasets
  • Consider memory usage when working with large arrays or objects
  • Log strategically - too many logs can slow down execution
  • Avoid deep recursion which can cause stack overflow errors

 

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:

  • Use console.log for values and console.error for errors
  • Log the structure of your data to understand what you're working with
  • Create helper functions to make debugging consistent
  • Catch errors at multiple levels to identify where problems occur
  • Include context in your log messages (e.g., item index or ID)
  • Check the execution logs in n8n for your console output
  • Test your code with simplified data first before handling complex structures

 

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:

  • Processing complex nested data structures consistently
  • Aggregating and summarizing data from multiple records
  • Creating reusable transformation functions that can be applied in different contexts
  • Building data pipelines with multiple processing stages

 

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:

  • Authenticated API requests using environment variables for credentials
  • Sequential processing of multiple API calls
  • Error handling for each integration point
  • Rate limiting prevention with small delays between requests
  • Comprehensive data enrichment from multiple sources

 

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:

  • Custom routing of items based on complex conditions
  • Parallel processing paths in your workflow
  • Prioritization of items
  • Branch metadata that can be used by subsequent nodes

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:

  • Consistency across your workflows
  • Time savings by not rewriting common code
  • Standardized approaches to common problems
  • Better maintainability when you need to update functionality

Consider creating templates for:

  • Data validation
  • API integration patterns
  • Common data transformations
  • Error handling
  • Logging and debugging

 

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:

  • Adding and configuring the Code node in your workflow
  • Working with the data structure in n8n
  • Processing items individually or as a batch
  • Manipulating data with JavaScript
  • Making HTTP requests to external APIs
  • Handling errors properly
  • Working with dates, arrays, and complex data structures
  • Using environment variables for security
  • Creating reusable functions and templates
  • Implementing complex workflow logic

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.

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Book a Free Consultation

Client trust and success are our top priorities

When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.

Rapid Dev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with. They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

CPO, Praction - Arkady Sokolov

May 2, 2023

Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost. He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Co-Founder, Arc - Donald Muir

Dec 27, 2022

Rapid Dev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space. They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Co-CEO, Grantify - Mat Westergreen-Thorne

Oct 15, 2022

Rapid Dev is an excellent developer for no-code and low-code solutions.
We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Co-Founder, Church Real Estate Marketplace - Emmanuel Brown

May 1, 2024 

Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 
This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Production Manager, Media Production Company - Samantha Fekete

Sep 23, 2022