
Obwohl n8n über 400+ integrierte Nodes bietet, gibt es Situationen, in denen Sie etwas benötigen, das noch nicht existiert. Vielleicht arbeiten Sie mit einer proprietären API, benötigen spezifische Datentransformationen oder möchten interne Systeme integrieren. Custom Nodes ermöglichen es Ihnen, maßgeschneiderte Lösungen zu entwickeln, die genau Ihren geschäftlichen Anforderungen entsprechen.
Ob Sie ein Entwickler sind, der die Funktionen von n8n erweitern möchte, oder ein Business-Anwender, der eine spezifische Integration benötigt – dieser Leitfaden führt Sie durch alles, was Sie über die Erstellung von Custom Nodes wissen müssen.
Bevor Sie mit der Entwicklung beginnen, ist es wichtig zu verstehen, wie n8n Nodes unter der Haube funktionieren.
In n8n ist ein Node eine funktionale Einheit, die eine spezifische Aufgabe ausführt, wie das Versenden einer E-Mail, das Durchführen einer HTTP-Anfrage oder das Abfragen einer Datenbank. Jeder Node verfügt über:






Vor der Ausführung:

Nach der Ausführung:


Jeder Custom Node besteht aus mehreren Schlüsseldateien:
my-custom-node/
├── package.json
├── nodes/
│ ├── MyNode/
│ │ ├── MyNode.node.ts # Hauptlogik des Nodes
│ │ ├── MyNode.node.json # Node-Metadaten
│ │ └── mynode.svg # Node-Icon
│ └── index.ts # Export-Definitionen
├── credentials/
│ ├── MyNodeApi.credentials.ts # Authentifizierungslogik
│ └── MyNodeApi.credentials.json # Credential-Metadaten
└── README.mdnpm install -g n8nVerwenden Sie das offizielle n8n-Starter-Template:
# Starter-Template klonen
git clone https://github.com/n8n-io/n8n-nodes-starter.git my-custom-node
cd my-custom-node
# Dependencies installieren
npm install
# Node bauen
npm run build# Ihren Node mit n8n verlinken
npm link
# Im n8n-Installationsverzeichnis (~/.n8n/custom)
npm link my-custom-nodeHinweis: Führen Sie npm link my-custom-node innerhalb von ~/.n8n/custom aus. Erstellen Sie das Verzeichnis (falls es nicht existiert), und starten Sie dann n8n neu.

n8n bietet zwei Haupt-Node-Building-Stile: deklarativ und programmatisch. Das Verstehen des Unterschieds ist entscheidend für den Erfolg Ihres Projekts.
Am besten geeignet für: Einfache API-Integrationen, CRUD-Operationen, unkomplizierte Datentransformationen
Eigenschaften:
Beispielstruktur:
// Deklarativer Node verwendet Routing
const routing = {
"user.create": {
request: {
method: "POST",
url: "/users",
body: {
name: "={{ $parameter.name }}",
email: "={{ $parameter.email }}",
},
},
},
};Am besten geeignet für: Komplexe Logik, benutzerdefinierte Datenverarbeitung, mehrere API-Aufrufe, fortgeschrittenes Error-Handling
Eigenschaften:
Beispielstruktur:
1// Programmatischer Node verwendet execute-Methode
2async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
3 const items = this.getInputData();
4 const returnData: INodeExecutionData[] = [];
5
6 for (let i = 0; i < items.length; i++) {
7 // Benutzerdefinierte Logik hier
8 const result = await this.performCustomOperation(items[i]);
9 returnData.push({ json: result });
10 }
11
12 return [returnData];
13}| Merkmal | Deklarativ | Programmatisch |
|---|---|---|
| Entwicklungsgeschwindigkeit | ⚡ Schnell | 🐢 Langsamer |
| Anpassungsfähigkeit | 🔒 Begrenzt | 🎯 Volle Kontrolle |
| Fehlerbehandlung | 🤖 Automatisch | 🔧 Manuell |
| Komplexe Logik | ❌ Nein | ✅ Ja |
| API-Paginierung | 🤖 Integriert | 🔧 Benutzerdefiniert |
Lassen Sie uns ein praktisches Beispiel erstellen: einen Microsoft Dynamics Lead Manager Node, der Leads in Dynamics 365 CRM erstellt, liest und aktualisiert.
So wird unser Microsoft Dynamics Lead Manager Node in der n8n-Oberfläche aussehen und funktionieren:

Erstellen Sie MicrosoftDynamicsLeads.node.json:
{
"displayName": "Microsoft Dynamics Leads",
"name": "microsoftDynamicsLeads",
"icon": "file:dynamics365.svg",
"group": ["transform"],
"version": 1,
"description": "Manage leads in Microsoft Dynamics 365 CRM",
"defaults": {
"name": "Dynamics Leads"
},
"inputs": ["main"],
"outputs": ["main"],
"credentials": [
{
"name": "microsoftDynamicsApi",
"required": true
}
],
"properties": [
{
"displayName": "Operation",
"name": "operation",
"type": "options",
"options": [
{
"name": "Create Lead",
"value": "create"
},
{
"name": "Read Lead",
"value": "read"
},
{
"name": "Update Lead",
"value": "update"
}
],
"default": "create",
"required": true
},
{
"displayName": "Lead ID",
"name": "leadId",
"type": "string",
"default": "",
"required": true,
"displayOptions": {
"show": {
"operation": ["read", "update"]
}
},
"description": "The ID of the lead to read or update"
},
{
"displayName": "First Name",
"name": "firstname",
"type": "string",
"default": "",
"required": true,
"displayOptions": {
"show": {
"operation": ["create", "update"]
}
},
"description": "First name of the lead"
},
{
"displayName": "Last Name",
"name": "lastname",
"type": "string",
"default": "",
"required": true,
"displayOptions": {
"show": {
"operation": ["create", "update"]
}
},
"description": "Last name of the lead"
},
{
"displayName": "Email",
"name": "emailaddress1",
"type": "string",
"default": "",
"displayOptions": {
"show": {
"operation": ["create", "update"]
}
},
"description": "Email address of the lead"
},
{
"displayName": "Company",
"name": "companyname",
"type": "string",
"default": "",
"displayOptions": {
"show": {
"operation": ["create", "update"]
}
},
"description": "Company name of the lead"
}
]
}Erstellen Sie MicrosoftDynamicsLeads.node.ts:
import {
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
NodeApiError,
NodeOperationError,
} from "n8n-workflow";
export class MicrosoftDynamicsLeads implements INodeType {
description: INodeTypeDescription = {
displayName: "Microsoft Dynamics Leads",
name: "microsoftDynamicsLeads",
icon: "file:dynamics365.svg",
group: ["transform"],
version: 1,
description: "Manage leads in Microsoft Dynamics 365 CRM",
defaults: {
name: "Dynamics Leads",
},
inputs: ["main"],
outputs: ["main"],
credentials: [
{
name: "microsoftDynamicsApi",
required: true,
},
],
properties: [
// Properties würden hier aus der JSON-Datei geladen
],
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
for (let i = 0; i < items.length; i++) {
try {
const operation = this.getNodeParameter("operation", i) as string;
const credentials = await this.getCredentials("microsoftDynamicsApi");
const instanceUrl = credentials.instanceUrl as string;
const accessToken = credentials.accessToken as string;
const baseUrl = `${instanceUrl}/api/data/v9.2`;
let result: any;
switch (operation) {
case "create": {
// Lead erstellen
const firstname = this.getNodeParameter("firstname", i) as string;
const lastname = this.getNodeParameter("lastname", i) as string;
const emailaddress1 = this.getNodeParameter(
"emailaddress1",
i,
) as string;
const companyname = this.getNodeParameter(
"companyname",
i,
) as string;
const leadData = {
firstname,
lastname,
emailaddress1,
companyname,
subject: `Lead: ${firstname} ${lastname}`,
};
result = await this.helpers.request({
method: "POST",
url: `${baseUrl}/leads`,
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
"OData-MaxVersion": "4.0",
"OData-Version": "4.0",
},
body: leadData,
json: true,
});
returnData.push({
json: {
operation: "create",
success: true,
leadId: result.leadid,
...leadData,
},
});
break;
}
case "read": {
// Lead abrufen
const leadId = this.getNodeParameter("leadId", i) as string;
result = await this.helpers.request({
method: "GET",
url: `${baseUrl}/leads(${leadId})`,
headers: {
Authorization: `Bearer ${accessToken}`,
"OData-MaxVersion": "4.0",
"OData-Version": "4.0",
},
json: true,
});
returnData.push({
json: {
operation: "read",
success: true,
lead: result,
},
});
break;
}
case "update": {
// Lead aktualisieren
const leadId = this.getNodeParameter("leadId", i) as string;
const firstname = this.getNodeParameter("firstname", i) as string;
const lastname = this.getNodeParameter("lastname", i) as string;
const emailaddress1 = this.getNodeParameter(
"emailaddress1",
i,
) as string;
const companyname = this.getNodeParameter(
"companyname",
i,
) as string;
const updateData = {
firstname,
lastname,
emailaddress1,
companyname,
};
await this.helpers.request({
method: "PATCH",
url: `${baseUrl}/leads(${leadId})`,
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
"OData-MaxVersion": "4.0",
"OData-Version": "4.0",
},
body: updateData,
json: true,
});
returnData.push({
json: {
operation: "update",
success: true,
leadId,
...updateData,
},
});
break;
}
default:
throw new NodeOperationError(
this.getNode(),
`Unknown operation: ${operation}`,
);
}
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: {
error: error.message,
statusCode: error.statusCode,
},
});
continue;
}
// Spezifisches Error Handling für Dynamics API
if (error.statusCode === 401) {
throw new NodeApiError(this.getNode(), error, {
message: "Authentication failed",
description: "Please check your credentials and access token",
});
}
if (error.statusCode === 404) {
throw new NodeOperationError(
this.getNode(),
"Lead not found or you do not have access",
);
}
throw new NodeOperationError(this.getNode(), error);
}
}
return [returnData];
}
}Erstellen Sie MicrosoftDynamicsApi.credentials.ts:
import { ICredentialType, INodeProperties } from "n8n-workflow";
export class MicrosoftDynamicsApi implements ICredentialType {
name = "microsoftDynamicsApi";
displayName = "Microsoft Dynamics 365 API";
documentationUrl = "https://learn.microsoft.com/en-us/dynamics365/";
properties: INodeProperties[] = [
{
displayName: "Instance URL",
name: "instanceUrl",
type: "string",
default: "https://yourorg.crm.dynamics.com",
required: true,
description: "Your Dynamics 365 instance URL",
placeholder: "https://yourorg.crm.dynamics.com",
},
{
displayName: "Client ID",
name: "clientId",
type: "string",
default: "",
required: true,
description: "OAuth 2.0 Client ID from Azure AD App Registration",
},
{
displayName: "Client Secret",
name: "clientSecret",
type: "string",
typeOptions: {
password: true,
},
default: "",
required: true,
description: "OAuth 2.0 Client Secret",
},
{
displayName: "Tenant ID",
name: "tenantId",
type: "string",
default: "",
required: true,
description: "Azure AD Tenant ID",
},
{
displayName: "Access Token",
name: "accessToken",
type: "string",
typeOptions: {
password: true,
},
default: "",
required: true,
description: "OAuth 2.0 Access Token for API requests",
},
];
}Aktualisieren Sie nodes/index.ts:
import { MicrosoftDynamicsLeads } from "./MicrosoftDynamicsLeads/MicrosoftDynamicsLeads.node";
export { MicrosoftDynamicsLeads };Aktualisieren Sie credentials/index.ts:
import { MicrosoftDynamicsApi } from "./MicrosoftDynamicsApi.credentials";
export { MicrosoftDynamicsApi };Lassen Sie Ihren Node sich basierend auf Benutzerauswahlen anpassen:
{
displayName: 'Operation',
name: 'operation',
type: 'options',
options: [
{
name: 'Create Lead',
value: 'create',
},
{
name: 'Read Lead',
value: 'read',
},
{
name: 'Update Lead',
value: 'update',
},
],
default: 'create',
},
{
displayName: 'Lead ID',
name: 'leadId',
type: 'string',
default: '',
displayOptions: {
show: {
operation: ['read', 'update'],
},
},
description: 'ID of the lead to read or update',
},
{
displayName: 'First Name',
name: 'firstname',
type: 'string',
default: '',
displayOptions: {
show: {
operation: ['create', 'update'],
},
},
description: 'First name of the lead',
},

Einige Nodes können mehrere Outputs basierend auf Bedingungen haben:

try {
const result = await this.helpers.request(requestOptions);
return result;
} catch (error) {
if (error.statusCode === 401) {
throw new NodeApiError(this.getNode(), error, {
message: "Authentication failed",
description:
"Please check your Dynamics 365 credentials and access token",
});
}
if (error.statusCode === 404) {
throw new NodeOperationError(
this.getNode(),
"Lead not found or you do not have access to this resource",
);
}
if (error.statusCode === 403) {
throw new NodeApiError(this.getNode(), error, {
message: "Insufficient permissions",
description:
"Your account does not have permission to perform this operation",
});
}
throw new NodeOperationError(this.getNode(), error);
}
Für komplexe APIs mit mehreren Endpoints:
const resourceMapping = {
leads: {
create: {
method: "POST",
endpoint: "/leads",
},
read: {
method: "GET",
endpoint: "/leads",
},
update: {
method: "PATCH",
endpoint: "/leads",
},
delete: {
method: "DELETE",
endpoint: "/leads",
},
},
accounts: {
create: {
method: "POST",
endpoint: "/accounts",
},
list: {
method: "GET",
endpoint: "/accounts",
},
},
contacts: {
create: {
method: "POST",
endpoint: "/contacts",
},
},
};Führen Sie Ihre n8n-Instanz lokal aus (npx n8n start) und prüfen Sie die UI. Ihr Node sollte unter "Custom Nodes" erscheinen.
# Bauen und testen
npm run build
npm run test
# n8n mit Ihrem Custom Node starten
npx n8n start
1. Console Logging verwenden:
console.log("Debug: Processing item", i, items[i]);2. API-Responses inspizieren:
const response = await this.helpers.request({
method: "GET",
url: "https://api.example.com/data",
json: true,
});
console.log("API Response:", response);3. Parameter-Validierung:
const leadId = this.getNodeParameter("leadId", i) as string;
if (!leadId) {
throw new NodeOperationError(this.getNode(), "Lead ID parameter is required");
}
const email = this.getNodeParameter("emailaddress1", i) as string;
if (email && !this.isValidEmail(email)) {
throw new NodeOperationError(this.getNode(), "Invalid email address format");
}| Problem | Lösung |
|---|---|
| Node erscheint nicht in der UI | package.json exports prüfen |
| Credential-Fehler | Credential-Typ-Matching verifizieren |
| Request-Fehler | Ordentliches Error-Handling hinzufügen |
| Parameter-Validierung | Input-Validierung hinzufügen |
# Für Entwicklung verlinken
npm link
# Im n8n-Verzeichnis
npm link your-node-package# Auf npm veröffentlichen
npm publish
# In n8n installieren
npm install your-custom-nodeErstellen Sie ein Dockerfile:
FROM n8nio/n8n:latest
# Custom Node kopieren
COPY /app/dist /usr/local/lib/node_modules/n8n/node_modules/your-custom-node
# Umgebungsvariablen setzen
ENV N8N_CUSTOM_EXTENSIONS="/usr/local/lib/node_modules/n8n/node_modules/your-custom-node"
EXPOSE 5678
CMD ["n8n"]version: "3.8"
services:
n8n:
image: n8nio/n8n:latest
ports:
- "5678:5678"
environment:
- N8N_CUSTOM_EXTENSIONS=/custom-nodes
volumes:
- ./custom-nodes:/custom-nodes
- n8n_data:/home/node/.n8n
depends_on:
- postgres
postgres:
image: postgres:13
environment:
POSTGRES_DB: n8n
POSTGRES_USER: n8n
POSTGRES_PASSWORD: n8n
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
n8n_data:
postgres_data:// Gut: Separate Methoden für verschiedene Operationen
class MyNode implements INodeType {
async execute(): Promise<INodeExecutionData[][]> {
const operation = this.getNodeParameter("operation", 0);
switch (operation) {
case "create":
return await this.create();
case "update":
return await this.update();
case "delete":
return await this.delete();
}
}
private async create(): Promise<INodeExecutionData[][]> {
// Create-Logik
}
}// Gut: Umfassende Fehlerbehandlung
try {
const result = await this.apiCall();
return result;
} catch (error) {
if (error.statusCode === 429) {
throw new NodeApiError(this.getNode(), error, {
message: "Rate limit exceeded",
description: "Please try again later",
});
}
throw new NodeOperationError(this.getNode(), error);
}// Gut: Inputs validieren
private validateParameters(params: any): void {
if (!params.email || !this.isValidEmail(params.email)) {
throw new NodeOperationError(this.getNode(), 'Valid email is required');
}
}// Items in Batches verarbeiten
const batchSize = 100;
const batches = this.chunkArray(items, batchSize);
for (const batch of batches) {
await this.processBatch(batch);
}
// Häufig abgerufene Daten cachen
private cache = new Map<string, any>();
private async getCachedData(key: string): Promise<any> {
if (this.cache.has(key)) {
return this.cache.get(key);
}
const data = await this.fetchData(key);
this.cache.set(key, data);
return data;
}// Gut: Sicherer Credential-Zugriff
const credentials = await this.getCredentials('myApiCredentials');
const apiKey = credentials.apiKey as string;
// Niemals Credentials loggen
console.log('Making API call...'); // API-Key NICHT loggen
// User-Inputs sanitizen
private sanitizeInput(input: string): string {
return input.replace(/[<>]/g, '');
}/**
* Erstellt oder aktualisiert einen Lead in Microsoft Dynamics 365
* @param leadData - Lead-Daten mit Vorname, Nachname, E-Mail und Firma
* @param operation - Operation (create oder update)
* @returns Erstellter oder aktualisierter Lead mit ID
*/
private async processLead(
leadData: any,
operation: 'create' | 'update'
): Promise<any> {
// Implementation
}Problem:
Der Custom Node wird nicht in der n8n-Oberfläche angezeigt
Lösungen:
package.json exports überprüfennpm link ausführenProblem:
Authentifizierungsfehler oder Credentials nicht gefunden
Lösungen:
Problem:
HTTP-Requests schlagen fehl oder haben Timeouts
Lösungen:
Die Erstellung von Custom Nodes in n8n eröffnet endlose Möglichkeiten für Workflow-Automatisierung. Ob Sie einfache API-Integrationen oder komplexe Geschäftslogik entwickeln – die Flexibilität und Leistungsfähigkeit von Custom Nodes kann transformieren, wie Sie Daten und Prozesse handhaben.
Die Zukunft der Workflow-Automatisierung liegt in Ihren Händen. Beginnen Sie noch heute mit der Entwicklung Ihrer Custom Nodes und erschließen Sie das volle Potenzial von n8n für Ihre spezifischen Anforderungen.