Custom Nodes

Overview

While n8n provides an extensive library of nodes, sometimes business requirements demand specialized functionality. Custom nodes bridge these gaps, providing tailored solutions that perfectly fit unique workflows and integration needs.

When to Build Custom Nodes

1. Proprietary Systems

2. Complex Business Logic

3. Performance Requirements

4. Integration Limitations

Custom Node Development Process

1. Requirements Analysis

2. Architecture Design

// Node structure template
class CustomNode {
  constructor() {
    this.description = {
      displayName: 'Custom Processing Node',
      name: 'customProcessing',
      group: ['transform'],
      version: 1,
      description: 'Custom data processing logic',
      defaults: {
        name: 'Custom Processing',
        color: '#666666'
      },
      inputs: ['main'],
      outputs: ['main'],
      properties: {
        // Node properties definition
      }
    };
  }
  
  async execute() {
    // Node execution logic
  }
}

3. Implementation

4. Testing & Validation

Custom Node Examples

1. Advanced Data Transformer

// Complex data transformation node
class AdvancedTransformer {
  constructor() {
    this.description = {
      displayName: 'Advanced Data Transformer',
      name: 'advancedTransformer',
      // ... other properties
      properties: {
        transformationRules: {
          type: 'json',
          label: 'Transformation Rules',
          default: {}
        },
        errorHandling: {
          type: 'options',
          options: [
            { name: 'Skip', value: 'skip' },
            { name: 'Error', value: 'error' },
            { name: 'Default', value: 'default' }
          ]
        }
      }
    };
  }
  
  async execute() {
    const items = this.getInputData();
    const rules = this.getNodeParameter('transformationRules');
    const errorHandling = this.getNodeParameter('errorHandling');
    
    const transformedItems = [];
    
    for (let i = 0; i < items.length; i++) {
      try {
        const transformed = this.applyRules(items[i].json, rules);
        transformedItems.push({ json: transformed });
      } catch (error) {
        if (errorHandling === 'error') {
          throw error;
        } else if (errorHandling === 'default') {
          transformedItems.push({ json: rules.defaultValue });
        }
        // Skip if errorHandling is 'skip'
      }
    }
    
    return this.prepareOutputData(transformedItems);
  }
  
  applyRules(data, rules) {
    // Complex transformation logic
    let result = { ...data };
    
    for (const [field, rule] of Object.entries(rules)) {
      switch (rule.type) {
        case 'map':
          result[field] = rule.mapping[result[field]] || result[field];
          break;
        case 'calculate':
          result[field] = this.calculate(result, rule.formula);
          break;
        case 'validate':
          if (!this.validate(result[field], rule.criteria)) {
            throw new Error(`Validation failed for ${field}`);
          }
          break;
        // ... more rule types
      }
    }
    
    return result;
  }
}

2. Smart Retry Node

// Intelligent retry mechanism
class SmartRetry {
  constructor() {
    this.description = {
      displayName: 'Smart Retry',
      name: 'smartRetry',
      // ... properties
      properties: {
        maxRetries: { type: 'number', default: 3 },
        backoffStrategy: {
          type: 'options',
          options: [
            { name: 'Linear', value: 'linear' },
            { name: 'Exponential', value: 'exponential' },
            { name: 'Fixed', value: 'fixed' }
          ]
        },
        baseDelay: { type: 'number', default: 1000 },
        retryConditions: {
          type: 'json',
          label: 'Retry Conditions',
          default: {
            statusCodes: [500, 502, 503, 504],
            errors: ['timeout', 'connection']
          }
        }
      }
    };
  }
  
  async execute() {
    const items = this.getInputData();
    const maxRetries = this.getNodeParameter('maxRetries');
    const strategy = this.getNodeParameter('backoffStrategy');
    const baseDelay = this.getNodeParameter('baseDelay');
    const conditions = this.getNodeParameter('retryConditions');
    
    const results = [];
    
    for (const item of items) {
      let lastError;
      let success = false;
      
      for (let attempt = 0; attempt <= maxRetries; attempt++) {
        try {
          const result = await this.executeWithRetry(item.json);
          results.push({ json: result });
          success = true;
          break;
        } catch (error) {
          lastError = error;
          
          if (attempt < maxRetries && this.shouldRetry(error, conditions)) {
            const delay = this.calculateDelay(attempt, strategy, baseDelay);
            await this.sleep(delay);
          }
        }
      }
      
      if (!success) {
        throw lastError;
      }
    }
    
    return this.prepareOutputData(results);
  }
  
  shouldRetry(error, conditions) {
    // Check if error matches retry conditions
    if (conditions.statusCodes.includes(error.statusCode)) {
      return true;
    }
    
    if (conditions.errors.some(type => error.message.includes(type))) {
      return true;
    }
    
    return false;
  }
  
  calculateDelay(attempt, strategy, baseDelay) {
    switch (strategy) {
      case 'linear':
        return baseDelay * (attempt + 1);
      case 'exponential':
        return baseDelay * Math.pow(2, attempt);
      case 'fixed':
        return baseDelay;
      default:
        return baseDelay;
    }
  }
}

3. Batch Processor Node

// Efficient batch processing
class BatchProcessor {
  constructor() {
    this.description = {
      displayName: 'Batch Processor',
      name: 'batchProcessor',
      // ... properties
      properties: {
        batchSize: { type: 'number', default: 100 },
        processingMode: {
          type: 'options',
          options: [
            { name: 'Parallel', value: 'parallel' },
            { name: 'Sequential', value: 'sequential' }
          ]
        },
        concurrency: { type: 'number', default: 5 }
      }
    };
  }
  
  async execute() {
    const items = this.getInputData();
    const batchSize = this.getNodeParameter('batchSize');
    const mode = this.getNodeParameter('processingMode');
    const concurrency = this.getNodeParameter('concurrency');
    
    const batches = this.createBatches(items, batchSize);
    const results = [];
    
    if (mode === 'parallel') {
      const batchPromises = batches.map(batch => 
        this.processBatch(batch, concurrency)
      );
      const batchResults = await Promise.all(batchPromises);
      results.push(...batchResults.flat());
    } else {
      for (const batch of batches) {
        const batchResults = await this.processBatch(batch, 1);
        results.push(...batchResults);
      }
    }
    
    return this.prepareOutputData(results);
  }
  
  createBatches(items, size) {
    const batches = [];
    for (let i = 0; i < items.length; i += size) {
      batches.push(items.slice(i, i + size));
    }
    return batches;
  }
  
  async processBatch(batch, concurrency) {
    // Process batch with specified concurrency
    const semaphore = new Semaphore(concurrency);
    
    const promises = batch.map(async (item) => {
      await semaphore.acquire();
      try {
        return await this.processItem(item);
      } finally {
        semaphore.release();
      }
    });
    
    return Promise.all(promises);
  }
}

Best Practices

1. Code Organization

2. Error Handling

3. Performance

4. Security

Deployment & Distribution

Community Sharing

Enterprise Distribution

Case Study: Custom ERP Integration Node

Challenge

Integrate with legacy ERP system using proprietary protocol and complex data structures

Solution

Developed custom node with:

Results

🤖

KC Assistant

Online
🤖

Hello! I'm KC's AI assistant. I can help you learn about KC's expertise in robotics, automation, and n8n workflows. How can I assist you today?