ComfyUI插件开发入门:手把手教你写第一个自定义节点

在AI图像生成的世界里,你有没有遇到过这样的场景?——想把一段文字加上特定前缀再送入模型,却发现现有的节点组合太繁琐;或者手头有个私有图像修复算法,却只能靠命令行调用,团队其他成员根本不会用。这时候,你就需要一个属于自己的自定义节点

而ComfyUI,正是那个让你“无代码搭建流程、有代码无限扩展”的理想平台。它不像传统WebUI那样把功能焊死在界面上,而是像搭乐高一样,每个处理步骤都是可拆卸的模块。更重要的是,这些模块你可以自己造。


想象一下,你正在为一家设计工作室搭建自动化出图流水线。客户上传文案后,系统自动拼接品牌标语、选择风格模板、生成高清图像并添加水印——整个过程不需要人工干预。这种级别的定制化,只有通过自定义节点才能实现。

那么,这个“节点”到底是什么?简单来说,它就是一个Python类,告诉ComfyUI:“我能接收什么输入、输出什么结果、叫什么名字、放在哪个分类下”。剩下的拖拽、连接、执行,全都由引擎自动完成。

我们先来看一个最基础的例子:字符串拼接节点。

# custom_nodes/comfyui_string_concat.py

class StringConcatNode:
    @classmethod
    def INPUT_TYPES(cls):
        return {
            "required": {
                "text_a": ("STRING", {"default": "Hello"}),
                "text_b": ("STRING", {"default": "World"})
            }
        }

    RETURN_TYPES = ("STRING",)
    RETURN_NAMES = ("concatenated_text",)
    CATEGORY = "utils"
    ICON = "TEXT"

    def execute(self, text_a, text_b):
        result = f"{text_a} {text_b}"
        print(f"[StringConcatNode] Output: {result}")
        return (result,)

NODE_CLASS_MAPPINGS = {
    "StringConcatNode": StringConcatNode
}

NODE_DISPLAY_NAME_MAPPINGS = {
    "StringConcatNode": "Concatenate Text"
}

别看代码不多,这里面藏着ComfyUI插件机制的核心逻辑。

首先,INPUT_TYPES 定义了这个节点需要哪些输入。这里有两个字符串参数,默认值分别是”Hello”和”World”。ComfyUI会根据这个定义,在前端自动生成两个文本框。如果你把类型改成 "INT""FLOAT",就会变成数字输入;如果是 "MODEL",则会出现模型选择下拉框。

RETURN_TYPESRETURN_NAMES 告诉系统你会返回什么。注意这是一个元组,意味着你可以同时输出多个值,比如一张图像和它的元数据。

CATEGORY = "utils" 决定了这个节点出现在左侧面板的哪个分组里。你可以写成 "my_tools""postprocessing" 甚至 "experimental",只要不冲突就行。图标 ICON 使用的是内置的Font Awesome标识,也可以自定义SVG路径。

真正干活的是 execute 方法。每次工作流运行到这个节点时,它就会被调用,拿到上游传来的数据,做完自己的事,然后把结果打包返回。就像流水线上的工人,接过前道工序的半成品,加工完再递给下一个环节。

最后两行是关键——NODE_CLASS_MAPPINGS 必须存在于模块顶层,否则ComfyUI根本找不到你的节点。这就像一份注册名单,告诉系统:“我这里有新员工报到”。

把这个文件放进 custom_nodes/ 目录,重启ComfyUI,你就能在搜索框里找到“Concatenate Text”了。连上两个文本节点试试?控制台应该会打印出拼接后的结果。

但别高兴得太早——现实中的节点远比这个复杂。比如你要加载一个PyTorch模型,总不能每次执行都重新加载一遍吧?那速度就慢死了。

正确的做法是在类外缓存模型实例:

# 模块级变量,仅加载一次
_cached_model = None

class ImageEnhanceNode:
    @classmethod
    def INPUT_TYPES(cls):
        return {
            "required": {
                "image": ("IMAGE",),
                "strength": ("FLOAT", {"default": 0.5, "min": 0.1, "max": 1.0})
            }
        }

    RETURN_TYPES = ("IMAGE",)
    CATEGORY = "image/postprocess"

    def execute(self, image, strength):
        global _cached_model
        if _cached_model is None:
            from mylib import load_enhancement_model
            _cached_model = load_enhancement_model("models/enhance_v2.pth")

        enhanced = _cached_model(image, strength)
        return (enhanced,)

你看,模型只在第一次调用时加载,后续直接复用。这对性能提升至关重要,特别是当你处理大模型或多批次任务时。

再说说插件的整体结构。很多人一开始就把所有代码塞进一个.py文件里,结果越写越乱。成熟的插件应该像这样组织:

custom_nodes/
└── comfyui-auto-watermark/
    ├── __init__.py
    ├── watermark_node.py
    └── web/
        └── extensions.js

__init__.py 是插件入口,可以用来做版本检查或延迟导入:

# __init__.py
def get_node_mappings():
    from .watermark_node import AutoWatermarkNode
    return {"AutoWatermarkNode": AutoWatermarkNode}

try:
    NODE_CLASS_MAPPINGS = get_node_mappings()
except Exception as e:
    print(f"[Error] Failed to load auto-watermark plugin: {e}")
    NODE_CLASS_MAPPINGS = {}

这样做有两个好处:一是启动时不会立即导入重型依赖(如OpenCV),加快加载速度;二是便于统一管理多个节点。

更进一步,如果你想给节点加个漂亮的前端按钮,或者监听画布事件,就需要用到 web/extensions.js

// web/extensions.js
app.registerExtension({
    name: "comfyui-auto-watermark",
    async beforeRegisterNodeDef(nodeType, nodeData, app) {
        if (nodeData.name === "AutoWatermarkNode") {
            const onExecuted = nodeType.prototype.onExecuted;
            nodeType.prototype.onExecuted = function(message) {
                onExecuted?.apply(this, arguments);
                // 添加自定义提示
                console.log("✅ 水印已自动去除");
            }
        }
    }
});

JavaScript部分虽然可选,但对于提升用户体验非常有用。比如你可以在这里动态更新预览图、添加进度条,甚至集成第三方UI库。

实际项目中,我还见过有人用自定义节点对接内部审批系统:当图像生成完成后,自动发起OA流程,等待主管确认后再释放最终版本。这类企业级集成,正是ComfyUI作为“生产平台”而非“玩具工具”的体现。

当然,开发过程中也有些坑要注意:

  • 不要阻塞主线程。如果节点要发HTTP请求或跑长时间任务,务必放到后台线程,否则整个UI都会卡住。
  • 做好异常捕获。用户可能输入非法数据,网络也可能中断。别让一个节点崩溃拖垮整条工作流。
  • 命名要有意义。避免用 Node1MyTool 这种名字,换成 RemoveBackgroundWithRemBG 才清晰。
  • 文档不能少。哪怕只是在GitHub放个README,说明怎么安装、有什么依赖、提供示例工作流。

我曾经参与过一个电商项目的图像生成系统,团队写了二十多个自定义节点:从商品图抠图、背景替换、多语言文案合成,到批量导出PSD。最终整个流程被封装成一条固定工作流,运营人员只需上传原始素材,点击运行,半小时后就能拿到上百张符合规范的宣传图。

这就是ComfyUI插件的价值——把复杂的AI能力,包装成普通人也能操作的黑箱模块。开发者专注底层逻辑,使用者专注业务目标,各司其职。

回过头看,从Stable Diffusion早期的WebUI,到现在基于节点图的工作流引擎,AI工具的演进方向已经很明确:既要开箱即用的便捷性,也要深度定制的自由度。而ComfyUI恰好站在了这个平衡点上。

未来,随着更多开发者加入生态,我们会看到更多惊艳的插件出现:实时语音驱动图像生成、3D模型纹理自动绘制、法律文书智能排版……可能性几乎是无限的。

所以,还等什么?现在就打开编辑器,创建你的第一个节点吧。哪怕只是写个 print("Hello, ComfyUI!"),那也是你迈向AI工程化的重要一步。

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐