TL;DR: n8n is a powerful automation tool, but sometimes the built-in nodes aren't enough. In this article, I'll show you how to develop your own custom nodes with TypeScript, test them locally, and deploy them to your n8n instance via npm or Docker.
Why you should only use TypeScript

🤔 Why Custom Nodes?

n8n offers hundreds of nodes for all kinds of services and integrations. But what if you want to connect an internal API that nobody else knows about? Or if you need very specific data processing that no existing node covers? That's exactly what Custom Nodes are for – you simply extend n8n with your own logic.

🧩 What Are Custom Nodes?

Custom Nodes are your own n8n extensions that you write in TypeScript. They behave just like the built-in nodes: they appear in the node menu, have input fields, can use credentials, and can be integrated into workflows.

Technically, you implement the INodeType interface from n8n and export your node as an npm package. n8n detects the package and loads it automatically on startup.

📋 Prerequisites

Before you get started, you'll need the following tools:

  • Node.js (version 18 or higher)
  • TypeScript – Custom Nodes are written in TypeScript
  • n8n CLI – installed via npm install -g n8n
  • n8n-node-dev – the official scaffolding tool: npx n8n-node-dev
  • Optional: Docker, if you want to run your nodes in a container

🏗️ Project Setup

The fastest way to set up a new node project is the official scaffolding:

# Create a new node project
npx n8n-node-dev new

# Alternatively: Clone the starter template
git clone https://github.com/n8n-io/n8n-nodes-starter.git
cd n8n-nodes-starter
npm install

The folder structure looks like this:

my-n8n-nodes/
├── credentials/
│   └── MyApiCredentials.credentials.ts
├── nodes/
│   └── MyNode/
│       ├── MyNode.node.ts
│       └── myNode.svg          # Icon
├── package.json
├── tsconfig.json
└── index.ts

The package.json is crucial: here you define under the n8n key which nodes and credentials your package provides:

{
  "name": "n8n-nodes-my-custom",
  "version": "0.1.0",
  "n8n": {
    "n8nNodesApiVersion": 1,
    "nodes": [
      "dist/nodes/MyNode/MyNode.node.js"
    ],
    "credentials": [
      "dist/credentials/MyApiCredentials.credentials.js"
    ]
  }
}

🔬 Anatomy of a Node

Every custom node implements the INodeType interface. The core consists of two parts:

  1. description – metadata, input fields, and the node configuration
  2. execute() – the method called during execution

💻 Code Example: A Simple HTTP Request Node

Here's a complete example of a custom node that makes an HTTP request to an API:

import {
  IExecuteFunctions,
  INodeExecutionData,
  INodeType,
  INodeTypeDescription,
} from 'n8n-workflow';

export class MyHttpNode implements INodeType {
  description: INodeTypeDescription = {
    displayName: 'My HTTP Node',
    name: 'myHttpNode',
    group: ['transform'],
    version: 1,
    description: 'Makes a simple HTTP request',
    defaults: {
      name: 'My HTTP Node',
    },
    inputs: ['main'],
    outputs: ['main'],
    credentials: [
      {
        name: 'myApiCredentials',
        required: true,
      },
    ],
    properties: [
      {
        displayName: 'Endpoint',
        name: 'endpoint',
        type: 'string',
        default: '/api/data',
        placeholder: '/api/endpoint',
        description: 'The API endpoint to call',
      },
      {
        displayName: 'Method',
        name: 'method',
        type: 'options',
        options: [
          { name: 'GET', value: 'GET' },
          { name: 'POST', value: 'POST' },
        ],
        default: 'GET',
        description: 'The HTTP method',
      },
    ],
  };

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

    const credentials = await this.getCredentials('myApiCredentials');
    const baseUrl = credentials.baseUrl as string;
    const apiKey = credentials.apiKey as string;

    for (let i = 0; i < items.length; i++) {
      const endpoint = this.getNodeParameter('endpoint', i) as string;
      const method = this.getNodeParameter('method', i) as string;

      const response = await this.helpers.request({
        method,
        url: `${baseUrl}${endpoint}`,
        headers: {
          'Authorization': `Bearer ${apiKey}`,
        },
        json: true,
      });

      returnData.push({ json: response });
    }

    return [returnData];
  }
}

🔑 Defining Credentials

To let your node use API keys or other access credentials, you create a Credential Type. This implements the ICredentialType interface:

import {
  ICredentialType,
  INodeProperties,
} from 'n8n-workflow';

export class MyApiCredentials implements ICredentialType {
  name = 'myApiCredentials';
  displayName = 'My API Credentials';
  properties: INodeProperties[] = [
    {
      displayName: 'Base URL',
      name: 'baseUrl',
      type: 'string',
      default: 'https://api.example.com',
    },
    {
      displayName: 'API Key',
      name: 'apiKey',
      type: 'string',
      typeOptions: { password: true },
      default: '',
    },
  ];
}

Credentials are stored encrypted by n8n. Users can enter them through the UI, and in your node you access them with this.getCredentials('myApiCredentials').

🧪 Local Testing

For local testing, you simply link your node package with npm link:

# In the node project
npm run build
npm link

# In the n8n installation directory
cd ~/.n8n
npm link n8n-nodes-my-custom

# Start n8n
n8n start

Your node should now appear in n8n's node menu. After making changes to the code, you'll need to run npm run build and restart n8n to test them.

Tip: Use n8n-node-dev build --watch for automatic rebuilding on changes.

🐳 Publishing & Docker

Once your node is ready, you have two options for distribution:

Option 1: Publish to npm

# Publish the package
npm publish

# Others can then install it
cd ~/.n8n
npm install n8n-nodes-my-custom

Important: The package name must start with n8n-nodes- for n8n to automatically detect it!

Option 2: Docker

For Docker-based n8n installations, you create your own Dockerfile:

Docker: Easy deployment of services
Docker simplifies service deployment with containers
FROM n8nio/n8n:latest

# Install custom nodes
USER root
RUN cd /usr/local/lib/node_modules/n8n && \
    npm install n8n-nodes-my-custom
USER node

Or you mount the nodes directly into the volume:

# docker-compose.yml
services:
  n8n:
    image: n8nio/n8n
    volumes:
      - n8n_data:/home/node/.n8n
      - ./custom-nodes:/home/node/.n8n/nodes

🎯 Conclusion

Custom Nodes are a powerful feature of n8n that allows you to tailor the platform exactly to your needs. With TypeScript and the INodeType interface, you have full control over the logic, UI fields, and credential management.

The workflow is surprisingly simple:

  1. Set up the project with n8n-node-dev
  2. Implement the node class with description and execute()
  3. Test locally with npm link
  4. Publish via npm or Docker

Have fun building your own nodes! 🚀

Artikel teilen:Share article: