如何在n8n中创建自定义节点?

在 n8n 中添加自定义节点:扩展你的自动化能力

n8n 是一款强大的开源工作流程自动化工具,而其最吸引人的特性之一就是其卓越的可扩展性。通过创建自定义节点,你可以连接任何你想集成的服务,或者实现任何特定的业务逻辑。本文将为你详细介绍两种在 n8n 中添加自定义节点的方法。

在开始之前,请确保你的本地 n8n 环境使用的是 pnpm 作为包管理器,因为 n8n 官方仓库采用 pnpm workspaces


方案一:使用官方模板仓库开发自定义节点库 (推荐)

这种方法允许你将自定义节点作为独立的 NPM 包进行开发和管理,更加灵活,也更容易分享和维护。

步骤详解

  1. 克隆官方模板项目
    通过 Git 将 n8n-nodes-starter 仓库克隆到本地。

    1
    2
    git clone https://github.com/n8n-io/n8n-nodes-starter.git n8n-nodes-custom-package
    cd n8n-nodes-custom-package
1

💡 提示: 我将克隆后的目录命名为 n8n-nodes-custom-package,以便与后续的包名对应。

  1. 修改 package.json 中的包名
    打开项目根目录下的 package.json 文件,将其中的 name 属性修改为一个有意义且唯一的名称。通常遵循 n8n-nodes-<your-package-name> 的命名规范。

    1
    2
    3
    4
    5
    6
    7
    8
    {
    // "name": "n8n-nodes-starter", // 原始名称
    "name": "n8n-nodes-custom", // 修改为你的自定义包名
    "version": "0.1.0",
    "description": "My custom n8n nodes",
    "private": false, // 如果打算发布到 npm, 需要设为 false
    // ... 其他配置
    }

    💡 提示: 模板项目中的 nodes/ 目录下已包含两个示例节点 (ExampleNodeHttpBin)。你可以根据需要修改它们,或者直接删除并添加自己的节点代码文件。

  2. 开发并构建你的节点
    n8n-nodes-custom-package 目录中,你可以开始编写你的自定义节点逻辑。完成修改后,执行以下命令来构建你的节点并将其全局链接到 pnpm 的 store:

    1
    2
    pnpm run build
    pnpm link --global
    • pnpm run build: 编译你的 TypeScript 节点文件为 JavaScript,并处理图标等资源。
    • pnpm link --global: 这会将你的 n8n-nodes-custom 包(即 package.json 中定义的 name)全局链接到 pnpm 的存储中,使其可被其他项目发现和使用。
  3. 将自定义节点导入本地 n8n
    现在,你需要将全局链接的自定义节点库导入到你的本地 n8n 实例中。n8n 通常会在其数据目录下的 custom 文件夹中寻找额外的节点。

    首先,确保该目录存在:

    1
    2
    mkdir -p ~/.n8n/custom/ # 适用于 macOS/Linux
    # 对于 Windows,通常是 %USERPROFILE%\.n8n\custom\

    然后,进入该目录并使用 pnpm link 命令链接你的自定义节点包:

    1
    2
    cd ~/.n8n/custom/
    pnpm link n8n-nodes-custom # 这里的 'n8n-nodes-custom' 是你在 package.json 中设置的 name
  4. 重新启动 n8n 服务
    返回你的 n8n 主项目目录,并使用 pnpm 启动 n8n:

    1
    2
    3
    4
    cd /path/to/your/n8n-repo # 你的 n8n 源码仓库路径
    pnpm start
    # 或者使用开发模式,更适合调试:
    # pnpm run dev

    验证: 在浏览器中打开 n8n UI (通常是 http://localhost:5678)。在节点选择列表中搜索你的节点 displayName (例如:“Example Node”或你自定义的节点名称),你应该就能看到它们了!


方案二:直接在 n8n 项目源码中编写节点代码

如果你正在对 n8n 核心代码进行深度开发,或者希望直接贡献新的内置节点,那么直接在 n8n 源码仓库中编写节点代码是更直接的方式。n8n 所有内置节点都位于其 packages/nodes-base/nodes 目录中。

步骤详解

创建节点代码文件
在你的本地 n8n 源码仓库中,导航到 packages/nodes-base/nodes/ 目录。
在该目录下创建一个新的子目录,例如 CustomNode
接着,在该 CustomNode 目录中创建你的节点文件,命名为 CustomNode.node.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// CustomNode.node.ts
import type {
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
INodePropertyOptions,
ILoadOptionsFunctions, // 导入 ILoadOptionsFunctions 类型
} from 'n8n-workflow';
import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';

export class CustomNode implements INodeType {
description: INodeTypeDescription = {
displayName: 'My Awesome Custom Node', // 在UI中显示的名称
name: 'myAwesomeCustomNode', // 内部唯一的名称,用于搜索和引用
icon: 'fa:magic', // 使用 FontAwesome 图标,或指定一个 SVG 文件路径 'file:customNode.svg'
group: ['transform'], // 节点分组
version: 1,
description: '我的自定义节点',
defaults: {
name: 'My Awesome Custom Node',
},
inputs: [NodeConnectionTypes.Main],
outputs: [NodeConnectionTypes.Main],
usableAsTool: true, // 如果节点可用作工具,请设置为 true
properties: [
{
displayName: '输入消息',
name: 'myString',
type: 'string',
default: '',
placeholder: '请在此输入一些文本',
description: '要处理的文本消息。',
},
// --- 示例:动态加载下拉选项 ---
{
displayName: '选择一个类别',
name: 'category',
type: 'options',
default: '',
description: '从外部 API 动态加载的类别列表。',
loadOptionsMethod: 'loadCategories', // 调用自定义方法加载选项
},
],
};

// 动态加载下拉选项的方法必须定义在 methods.loadOptions 属性下
methods = {
loadOptions: {
async loadCategories(
this: ILoadOptionsFunctions, // 使用 ILoadOptionsFunctions 类型
): Promise<INodePropertyOptions[]> {
try {
// 模拟一个 API 调用
const response = await this.helpers.httpRequest({
method: 'GET',
uri: 'https://jsonplaceholder.typicode.com/todos?_limit=5', // 示例公共 API
json: true,
});

// 将 API 响应转换为 n8n 期望的格式
if (Array.isArray(response)) {
return response.map(item => ({
name: item.title, // 显示给用户的文本
value: String(item.id), // 实际传递的值
}));
}
} catch (error) {
// 使用 this.logger 而不是 console.error
this.logger.error('Error loading categories:', error);
throw new NodeOperationError(this.getNode(), `Failed to load categories: ${error.message}`);
}
return []; // 发生错误或没有数据时返回空数组
},
},
};

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

for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
try {
const myString = this.getNodeParameter('myString', itemIndex, '') as string;
const selectedCategory = this.getNodeParameter('category', itemIndex, '') as string; // 获取动态选择的类别

const newItem = {
json: {
originalInput: items[itemIndex].json, // 保留原始输入
processedString: `Processed: ${myString}`,
selectedCategory: selectedCategory,
},
};
returnData.push(newItem);
} catch (error) {
// 优雅地处理错误,确保工作流程的健壮性
if (this.continueOnFail()) {
returnData.push({ json: {}, error, pairedItem: itemIndex });
} else {
throw new NodeOperationError(this.getNode(), error, { itemIndex });
}
}
}

return [returnData];
}
}

验证 packages/nodes-base/tsconfig.json 配置
确保在 /your-n8n-repo/packages/nodes-base/tsconfig.json 文件中,include 属性包含了 nodes/**/*.ts,这样你的自定义节点文件才会被 TypeScript 编译器找到并处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./",
"outDir": "./dist",
// ...
},
"include": [
"src/**/*.ts",
"nodes/**/*.ts" // 确保有这一行
],
"exclude": [
"src/**/*.spec.ts",
"nodes/**/*.spec.ts",
"dist/**"
]
}

构建并启动 n8n
在 n8n 源码仓库的根目录 (/your-n8n-repo) 执行以下命令:

1
2
3
pnpm run build # 编译所有代码,包括你的新节点
pnpm run dev # 启动开发模式,支持文件热重载,方便调试
# 或者 pnpm start 启动生产模式
  • pnpm run build: 这会编译整个 packages/nodes-base 包,你的 CustomNode.node.ts 将被编译成 CustomNode.node.jsCustomNode.node.d.ts,并放置在 packages/nodes-base/dist/nodes/CustomNode/ 目录下。

💡 重要检查: 在执行 pnpm run build 后,请务必检查 packages/nodes-base/dist/nodes/CustomNode/ 目录,确保 CustomNode.node.jsCustomNode.node.d.ts 都已正确生成。如果没有,请仔细检查 tsconfig.json 配置和文件路径。

  • 一切正常的话,packages/nodes-base/package.json 文件的n8n.nodes字段中会包含打包后的节点文件。如果没有则需要手动添加
    1
    2
    3
    4
    5
    6
    7
    8
    {
    "n8n": {
    "nodes": [
    ...,
    "dist/nodes/CustomNode/CustomNode.node.js"
    ]
    }
    }

验证:
打开 n8n UI,在节点面板中搜索你的节点 displayName (例如:”My Awesome Custom Node” 或 name “myAwesomeCustomNode”)。你应该就能看到并使用它了。