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
Poll Nodes
Regular Nodes
Webhook 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
Poll nodes check external sources at regular intervals: interface INodeType {
poll ?: ( this : IPollFunctions ) => Promise < INodeExecutionData [][] | null >;
}
Examples:
RSS Feed polling
Email inbox polling
API endpoint polling
Key Characteristics:
Called on a schedule
Return null if no new data
Have poll property defined on node type
Regular nodes process data as it flows through the workflow: interface INodeType {
execute ?: ( this : IExecuteFunctions ) => Promise < INodeExecutionData [][]>;
}
Examples:
HTTP Request node
Set node (data transformation)
IF node (conditional routing)
Code node (custom JavaScript)
Key Characteristics:
Process input data and return output
Can have multiple inputs and outputs
Execute synchronously in workflow order
Webhook nodes handle incoming HTTP requests: interface INodeType {
webhook ?: ( this : IWebhookFunctions ) => Promise < IWebhookResponseData >;
}
Examples:
n8n-nodes-base.webhook - Generic webhook
n8n-nodes-base.formTrigger - Form webhooks
Key Characteristics:
Respond to HTTP requests
Can return immediate responses
Have webhook paths and methods
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
String Parameter
Number Parameter
Options Parameter
Collection Parameter
{
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 }]
]
}
};
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:
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 ];
}
webhook
Handle incoming webhook requests: async webhook ( this : IWebhookFunctions ): Promise < IWebhookResponseData > {
const req = this . getRequestObject ();
const body = this . getBodyData ();
return {
workflowData : [
[{ json: body }]
]
};
}
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
}
};
}
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
Use descriptive node names : Names should clearly indicate the node’s purpose
Validate parameters : Always validate user input in node implementations
Handle errors gracefully : Use try-catch and return meaningful error messages
Respect disabled nodes : Check node.disabled before processing
Use paired items : Maintain data lineage with pairedItem for debugging
Document parameters : Provide clear descriptions for all parameters
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