Skip to main content

Node Types

This guide covers the core interfaces and patterns for implementing n8n nodes.

INodeType Interface

Every n8n node implements the INodeType interface, which defines the node’s structure and behavior.
description
INodeTypeDescription
required
Node metadata, UI configuration, and parameter definitions
execute
function
Main execution function for programmatic nodes
execute?(this: IExecuteFunctions): Promise<NodeOutput>
poll
function
Polling function for trigger nodes with polling: true
poll?(this: IPollFunctions): Promise<INodeExecutionData[][] | null>
trigger
function
Generic trigger function for event-based triggers
trigger?(this: ITriggerFunctions): Promise<ITriggerResponse | undefined>
webhook
function
Webhook handler for webhook-based triggers
webhook?(this: IWebhookFunctions): Promise<IWebhookResponseData>
methods
object
Optional methods for dynamic options, credential testing, and resource mapping
  • loadOptions: Dynamic parameter options
  • listSearch: Searchable list options
  • credentialTest: Credential validation
  • resourceMapping: Resource field mapping
  • actionHandler: Custom button actions
webhookMethods
object
Webhook lifecycle methods (checkExists, create, delete)

INodeTypeDescription

The description object defines all node metadata and UI configuration.
export const versionDescription: INodeTypeDescription = {
  displayName: 'Edit Fields (Set)',
  name: 'set',
  icon: 'fa:pen',
  iconColor: 'blue',
  group: ['input'],
  version: [3, 3.1, 3.2, 3.3, 3.4],
  description: 'Modify, add, or remove item fields',
  subtitle: '={{$parameter["mode"]}}',
  defaults: {
    name: 'Edit Fields',
  },
  inputs: [NodeConnectionTypes.Main],
  outputs: [NodeConnectionTypes.Main],
  properties: [
    // Parameter definitions...
  ],
};

Core Properties

displayName
string
required
Human-readable name shown in the UI
name
string
required
Internal identifier (kebab-case)
icon
Icon
Node icon. Can be:
  • fa:icon-name - FontAwesome icon
  • file:icon.svg - Custom SVG file
  • node:package.nodeName - Icon from another node
iconColor
ThemeIconColor
Icon color from design system: gray, blue, green, red, orange, purple, etc.
group
NodeGroupType[]
required
Node categories: input, output, transform, trigger, schedule
version
number | number[]
required
Node version(s). Use array for light versioning: [1, 2, 3]
inputs
Array<NodeConnectionType | INodeInputConfiguration>
required
Input connections. Common types:
  • NodeConnectionTypes.Main - Standard data flow
  • NodeConnectionTypes.AiLanguageModel - AI model input
  • NodeConnectionTypes.AiMemory - AI memory input
outputs
Array<NodeConnectionType | INodeOutputConfiguration>
required
Output connections. Same types as inputs.
properties
INodeProperties[]
required
Node parameters definition (see Node Parameters guide)
credentials
INodeCredentialDescription[]
Required credentials for the node

Advanced Properties

polling
boolean
Set to true for polling trigger nodes
webhooks
IWebhookDescription[]
Webhook configuration for webhook triggers
requestDefaults
HttpRequestOptions
Default HTTP request options for declarative nodes
maxNodes
number
Maximum instances of this node allowed in a workflow
hooks
object
Lifecycle hooks: activate, deactivate
features
NodeFeaturesDefinition
Feature flags with version-based conditions

Node Types

Programmatic Nodes

Nodes with custom execution logic implement the execute method.
packages/nodes-base/nodes/Set/v2/SetV2.node.ts
export class SetV2 implements INodeType {
  description: INodeTypeDescription = versionDescription;

  async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
    const items = this.getInputData();
    const returnData: INodeExecutionData[] = [];

    const mode = this.getNodeParameter('mode', 0) as Mode;

    if (mode === 'manual') {
      // Process each item manually
      for (let i = 0; i < items.length; i++) {
        const options = this.getNodeParameter('options', i, {}) as SetNodeOptions;
        const fields = this.getNodeParameter('fields', i, []) as SetField[];
        
        const newItem = await manual.execute.call(
          this,
          items[i],
          i,
          options,
          fields,
        );
        returnData.push(newItem);
      }
    }

    return [returnData];
  }
}

Declarative Nodes

Declarative nodes use requestDefaults and routing configuration instead of implementing execute.
Example Declarative Node
{
  displayName: 'HTTP Request',
  name: 'httpRequest',
  requestDefaults: {
    baseURL: '={{$credentials.domain}}',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
    },
  },
  properties: [
    {
      displayName: 'Resource',
      name: 'resource',
      type: 'options',
      options: [
        { name: 'User', value: 'user' },
        { name: 'Project', value: 'project' },
      ],
      default: 'user',
      noDataExpression: true,
    },
    {
      displayName: 'Operation',
      name: 'operation',
      type: 'options',
      displayOptions: {
        show: { resource: ['user'] },
      },
      options: [
        {
          name: 'Get',
          value: 'get',
          routing: {
            request: {
              method: 'GET',
              url: '/users/={{$parameter.userId}}',
            },
          },
        },
      ],
      default: 'get',
    },
  ],
}

Trigger Nodes

Webhook Triggers

Webhook Trigger Implementation
export class WebhookTrigger implements INodeType {
  description: INodeTypeDescription = {
    displayName: 'Webhook Trigger',
    name: 'webhookTrigger',
    group: ['trigger'],
    version: 1,
    description: 'Starts workflow on webhook event',
    defaults: { name: 'Webhook Trigger' },
    inputs: [],
    outputs: [NodeConnectionTypes.Main],
    webhooks: [
      {
        name: 'default',
        httpMethod: 'POST',
        responseMode: 'onReceived',
        path: 'webhook',
      },
    ],
    properties: [],
  };

  async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
    const bodyData = this.getBodyData();
    const headerData = this.getHeaderData();

    return {
      workflowData: [
        [
          {
            json: {
              body: bodyData,
              headers: headerData,
            },
          },
        ],
      ],
    };
  }

  webhookMethods = {
    default: {
      async checkExists(this: IHookFunctions): Promise<boolean> {
        // Check if webhook exists on external service
        return true;
      },
      async create(this: IHookFunctions): Promise<boolean> {
        // Create webhook on external service
        return true;
      },
      async delete(this: IHookFunctions): Promise<boolean> {
        // Delete webhook from external service
        return true;
      },
    },
  };
}

Polling Triggers

Polling Trigger Implementation
export class PollingTrigger implements INodeType {
  description: INodeTypeDescription = {
    displayName: 'Polling Trigger',
    name: 'pollingTrigger',
    group: ['trigger'],
    version: 1,
    description: 'Polls for new data',
    polling: true,
    defaults: { name: 'Polling Trigger' },
    inputs: [],
    outputs: [NodeConnectionTypes.Main],
    properties: [
      {
        displayName: 'Polling Interval',
        name: 'pollInterval',
        type: 'number',
        default: 60,
        description: 'How often to check for new data (seconds)',
      },
    ],
  };

  async poll(this: IPollFunctions): Promise<INodeExecutionData[][] | null> {
    const staticData = this.getWorkflowStaticData('node');
    let lastId = staticData.lastId as number | undefined;

    // Fetch new items since lastId
    const newItems = await this.helpers.httpRequest({
      method: 'GET',
      url: `https://api.example.com/items?since=${lastId || 0}`,
    });

    if (newItems.length === 0) {
      return null;
    }

    // Update state
    staticData.lastId = newItems[newItems.length - 1].id;

    return [
      newItems.map((item: any) => ({
        json: item,
      })),
    ];
  }
}

Versioned Nodes

For major changes, use the VersionedNodeType class.
packages/nodes-base/nodes/Set/Set.node.ts
import { VersionedNodeType } from 'n8n-workflow';
import { SetV1 } from './v1/SetV1.node';
import { SetV2 } from './v2/SetV2.node';

export class Set extends VersionedNodeType {
  constructor() {
    const baseDescription: INodeTypeBaseDescription = {
      displayName: 'Set',
      name: 'set',
      icon: 'fa:pen',
      group: ['input'],
      description: 'Add or edit fields on an input item',
      defaultVersion: 3.4,
    };

    const nodeVersions: IVersionedNodeType['nodeVersions'] = {
      1: new SetV1(baseDescription),
      2: new SetV1(baseDescription),
      3: new SetV2(baseDescription),
      3.1: new SetV2(baseDescription),
      3.2: new SetV2(baseDescription),
      3.3: new SetV2(baseDescription),
      3.4: new SetV2(baseDescription),
    };

    super(nodeVersions, baseDescription);
  }
}

Dynamic Methods

Nodes can implement dynamic behavior through the methods object.

Load Options

Provide dynamic dropdown options:
Load Options Method
methods = {
  loadOptions: {
    async getUsers(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
      const credentials = await this.getCredentials('myApi');
      const users = await this.helpers.httpRequest({
        method: 'GET',
        url: `${credentials.baseUrl}/users`,
      });

      return users.map((user: any) => ({
        name: user.name,
        value: user.id,
      }));
    },
  },
};
Searchable resource lists:
List Search Method
methods = {
  listSearch: {
    async searchUsers(
      this: ILoadOptionsFunctions,
      filter?: string,
    ): Promise<INodeListSearchResult> {
      const credentials = await this.getCredentials('myApi');
      const response = await this.helpers.httpRequest({
        method: 'GET',
        url: `${credentials.baseUrl}/users/search`,
        qs: { q: filter },
      });

      return {
        results: response.users.map((user: any) => ({
          name: user.name,
          value: user.id,
          url: user.profileUrl,
        })),
      };
    },
  },
};

Credential Test

Validate credentials:
Credential Test
methods = {
  credentialTest: {
    async testApiCredentials(
      this: ICredentialTestFunctions,
      credential: ICredentialsDecrypted,
    ): Promise<INodeCredentialTestResult> {
      const credentials = credential.data as IDataObject;
      
      try {
        await this.helpers.request({
          method: 'GET',
          url: `${credentials.baseUrl}/auth/verify`,
          headers: {
            'Authorization': `Bearer ${credentials.apiKey}`,
          },
        });
        
        return {
          status: 'OK',
          message: 'Authentication successful',
        };
      } catch (error) {
        return {
          status: 'Error',
          message: 'Invalid credentials',
        };
      }
    },
  },
};

Best Practices

  • Use clear, descriptive displayName values
  • Set appropriate group for node categorization
  • Provide meaningful description and subtitle
  • Use semantic versioning for version

See Also