Skip to main content

Nodes Overview

Nodes are the building blocks of n8n workflows. Each node represents a discrete action or operation, such as making an HTTP request, processing data, or triggering workflows. This guide covers node structure, types, and how to work with them.

Node Structure

Every node in n8n follows the INode interface:
interface INode {
  name: string;                    // Unique identifier within workflow
  type: string;                    // Node type (e.g., 'n8n-nodes-base.httpRequest')
  typeVersion: number;             // Version of the node type
  position: [number, number];      // Visual position [x, y]
  parameters: INodeParameters;     // Node configuration
  disabled?: boolean;              // If true, node is skipped
  notes?: string;                  // Documentation notes
  notesInFlow?: boolean;           // Show notes in canvas
  retryOnFail?: boolean;           // Enable automatic retry
  maxTries?: number;               // Maximum retry attempts
  waitBetweenTries?: number;       // Delay between retries (ms)
  alwaysOutputData?: boolean;      // Output data even on error
  executeOnce?: boolean;           // Run once for all items
  continueOnFail?: boolean;        // Continue workflow on error
  credentials?: INodeCredentials;  // Attached credentials
}

Node Types

n8n has several categories of nodes:
Trigger nodes start workflow executions automatically based on events:
interface INodeType {
  trigger?: (this: ITriggerFunctions) => Promise<ITriggerResponse | undefined>;
}
Examples:
  • n8n-nodes-base.webhook - HTTP webhooks
  • n8n-nodes-base.scheduleTrigger - Cron-based scheduling
  • n8n-nodes-base.formTrigger - Form submissions
  • n8n-nodes-base.manualTrigger - Manual execution
Key Characteristics:
  • Return a cleanup function to stop the trigger
  • Can emit data multiple times
  • Have trigger property defined on node type

Node Parameters

Node parameters define how a node behaves. The INodeProperties interface describes available parameters:
const nodeProperties: INodeProperties[] = [
  {
    displayName: 'URL',
    name: 'url',
    type: 'string',
    required: true,
    default: '',
    placeholder: 'https://api.example.com',
    description: 'The URL to make the request to'
  },
  {
    displayName: 'Method',
    name: 'method',
    type: 'options',
    options: [
      { name: 'GET', value: 'GET' },
      { name: 'POST', value: 'POST' },
      { name: 'PUT', value: 'PUT' },
      { name: 'DELETE', value: 'DELETE' }
    ],
    default: 'GET'
  }
];

Parameter Types

{
  displayName: 'API Key',
  name: 'apiKey',
  type: 'string',
  typeOptions: {
    password: true  // Mask in UI
  },
  default: ''
}

Getting Node Parameters

The NodeHelpers.getNodeParameters() function merges defaults with user values:
import { NodeHelpers } from 'n8n-workflow';

// Get parameters with defaults applied
const parameters = NodeHelpers.getNodeParameters(
  nodeType.description.properties,  // Parameter definitions
  node.parameters,                  // User-provided values
  true,                            // Include defaults
  false,                           // Don't throw on missing
  node,                            // The node instance
  nodeType.description             // Node type description
);

Node Connections

Connections define data flow between nodes. Each connection specifies:
interface IConnection {
  node: string;              // Target node name
  type: NodeConnectionType;  // Connection type ('main', 'ai_agent', etc.)
  index: number;            // Output/input index
}

// Connection types
const NodeConnectionTypes = {
  Main: 'main',              // Standard data flow
  AiAgent: 'ai_agent',       // AI agent connections
  AiTool: 'ai_tool',         // AI tool connections
  AiDocument: 'ai_document', // Document connections
  AiMemory: 'ai_memory'      // Memory connections
};

Multiple Outputs

Nodes can have multiple outputs for different routing scenarios:
// IF node with true/false outputs
const connections = {
  'IF': {
    'main': [
      // Output 0 (true branch)
      [{ node: 'ProcessTrue', type: 'main', index: 0 }],
      // Output 1 (false branch)
      [{ node: 'ProcessFalse', type: 'main', index: 0 }]
    ]
  }
};

Multiple Inputs

Nodes can accept data from multiple sources:
// Merge node combining two inputs
const connections = {
  'Source1': {
    'main': [[{ node: 'Merge', type: 'main', index: 0 }]]
  },
  'Source2': {
    'main': [[{ node: 'Merge', type: 'main', index: 1 }]]
  }
};

Node Execution Data

Data flows between nodes as INodeExecutionData:
interface INodeExecutionData {
  json: IDataObject;                    // JSON data
  binary?: IBinaryKeyData;              // Binary files
  pairedItem?: IPairedItemData;         // Link to source item
  error?: Error;                        // Error information
  metadata?: ITaskMetadata;             // Execution metadata
}

// Example execution data
const executionData: INodeExecutionData[] = [
  {
    json: {
      id: 1,
      name: 'John Doe',
      email: 'john@example.com'
    },
    binary: {
      data: {
        data: 'base64string...',
        mimeType: 'image/png',
        fileName: 'profile.png'
      }
    },
    pairedItem: {
      item: 0,  // Source item index
      input: 0  // Source input index
    }
  }
];
The pairedItem property tracks data lineage, allowing you to trace which output items came from which input items.

Node Outputs

Nodes return data in a specific format:
// Single output
return [executionData];

// Multiple outputs (e.g., IF node)
return [
  trueItems,   // Output 0
  falseItems   // Output 1
];

// No data
return [[]];

Node Output Configuration

Node types can define dynamic outputs:
const nodeType: INodeType = {
  description: {
    outputs: [
      'main',  // First output
      'main'   // Second output
    ],
    // Or dynamic outputs
    outputs: '={{$parameter["outputs"]}}',
    // Or output names
    outputNames: ['Success', 'Error']
  }
};

Common Node Patterns

Cron/Schedule Node

The Schedule Trigger uses cron expressions:
// From node-helpers.ts - Cron node configuration
const cronNodeOptions: INodePropertyCollection[] = [
  {
    name: 'item',
    displayName: 'Item',
    values: [
      {
        displayName: 'Mode',
        name: 'mode',
        type: 'options',
        options: [
          { name: 'Every Minute', value: 'everyMinute' },
          { name: 'Every Hour', value: 'everyHour' },
          { name: 'Every Day', value: 'everyDay' },
          { name: 'Every Week', value: 'everyWeek' },
          { name: 'Every Month', value: 'everyMonth' },
          { name: 'Custom', value: 'custom' }
        ],
        default: 'everyDay'
      },
      {
        displayName: 'Hour',
        name: 'hour',
        type: 'number',
        typeOptions: { minValue: 0, maxValue: 23 },
        default: 14
      },
      {
        displayName: 'Cron Expression',
        name: 'cronExpression',
        type: 'string',
        displayOptions: { show: { mode: ['custom'] } },
        default: '* * * * * *',
        description: 'Custom cron expression'
      }
    ]
  }
];

Error Handling in Nodes

Nodes can handle errors at the node level:
const node: INode = {
  name: 'HTTP Request',
  type: 'n8n-nodes-base.httpRequest',
  typeVersion: 1,
  position: [450, 300],
  parameters: {},
  // Error handling options
  retryOnFail: true,          // Enable retry on failure
  maxTries: 3,                // Maximum 3 attempts
  waitBetweenTries: 1000,     // Wait 1 second between tries
  continueOnFail: false,      // Stop workflow on failure
  alwaysOutputData: false     // Don't output data on error
};

Node Type Methods

Node types can implement various lifecycle methods:
1

execute

Main execution method for processing data:
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
  const items = this.getInputData();
  const returnData: INodeExecutionData[] = [];

  for (let i = 0; i < items.length; i++) {
    // Process each item
    const result = await processItem(items[i]);
    returnData.push({ json: result });
  }

  return [returnData];
}
2

webhook

Handle incoming webhook requests:
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
  const req = this.getRequestObject();
  const body = this.getBodyData();

  return {
    workflowData: [
      [{ json: body }]
    ]
  };
}
3

trigger

Set up event-based triggers:
async trigger(this: ITriggerFunctions): Promise<ITriggerResponse> {
  const emit = this.emit;

  // Set up event listener
  const listener = (data: any) => {
    emit([[{ json: data }]]);
  };

  // Return cleanup function
  return {
    closeFunction: async () => {
      // Remove listener
    }
  };
}
4

poll

Poll external sources:
async poll(this: IPollFunctions): Promise<INodeExecutionData[][] | null> {
  const lastPoll = this.getWorkflowStaticData('node').lastPoll;
  const data = await fetchNewData(lastPoll);

  if (data.length === 0) {
    return null;  // No new data
  }

  return [data.map(item => ({ json: item }))];
}

Best Practices

  1. Use descriptive node names: Names should clearly indicate the node’s purpose
  2. Validate parameters: Always validate user input in node implementations
  3. Handle errors gracefully: Use try-catch and return meaningful error messages
  4. Respect disabled nodes: Check node.disabled before processing
  5. Use paired items: Maintain data lineage with pairedItem for debugging
  6. Document parameters: Provide clear descriptions for all parameters
  7. Test with different data types: Ensure nodes handle various input scenarios
When developing nodes, use the node-dev CLI tool:
pnpm --filter n8n-node-dev exec n8n-node-dev

Next Steps

Expressions

Use expressions to work with dynamic data

Error Handling

Implement error handling and retry logic