Skip to Content
Ⅳ · 扩展与边界14 · 自定义节点装载

14 · 自定义节点发现与装载

ComfyUI 的生态不是一个受限 DSL,而是把 Python 模块导入主进程。因此“节点为什么没出现”和“节点是否可信”都要从装载链分析,而不是只看类定义。

两个扩展时点

prestartup_script.py 与节点模块 import 都能执行任意 Python,只是发生时间不同。

路径来源

folder_paths.get_folder_paths("custom_nodes") 可返回一个或多个根目录,来源包括默认目录与 extra model paths 配置。加载器对每个根执行目录枚举。

候选可以是:

  • 单个 .py 文件;
  • 包含 __init__.py 的目录;
  • 非 Python 文件会跳过;
  • 名字以 .disabled 结尾会跳过;
  • __pycache__ 被排除。
nodes.pyL2332-2380候选发现、禁用/白名单/Manager policy 与加载计时。

三层启用策略

.disabled

本地显式禁用单个扩展。它基于目录/文件名,是最直观的故障隔离手段。

--disable-all-custom-nodes + whitelist

默认全部跳过,只允许白名单。适合排障或服务部署的 allowlist 模式。

Manager policy

启用 ComfyUI Manager 时,可由 manager 判断模块是否应被阻止。它在 import 前执行,能避免已知禁用扩展代码进入进程。

三层都只能决定“是否导入”,不能把已导入代码变成沙箱。

动态 import

load_custom_node() 为文件或包创建 importlib spec,构造 module,先放入 sys.modules,再 exec_module()。

nodes.pyL2236-2330模块加载、Web 注册、V1/V3 契约与异常隔离。

为什么先放 sys.modules

包内相对导入、循环导入和 Python module identity 依赖它。代价是 import 中途失败可能留下部分初始化模块状态;错误被记录后,不能假设重试一定像新进程一样干净。

module name 规范化

目录路径中的点会替换,文件与包走不同 spec;加载成功后 LOADED_MODULE_DIRS 保存模块名到绝对目录,供资源/诊断使用。

V1 注册分支

模块若导出非空 NODE_CLASS_MAPPINGS:

  1. 遍历 (node_id, class);
  2. 不允许覆盖启动前记录的核心 node IDs;
  3. 写入全局 NODE_CLASS_MAPPINGS;
  4. 给 class 写 RELATIVE_PYTHON_MODULE;
  5. 可合并 NODE_DISPLAY_NAME_MAPPINGS;
  6. 成功后立即返回。

核心映射可见于 nodes.py 的 KSampler、CheckpointLoader、SaveImage 等注册。

nodes.pyL2058-2140内建 node ID 到 class 的主映射。

ID 冲突

加载器显式保护核心 ID;两个第三方扩展之间的同名仍可能受加载顺序影响。扩展应使用稳定命名空间,如 Acme_ImageAnnotator,而不是通用 ProcessImage。

目录枚举顺序不应被当成可靠的覆盖机制。

V3 注册分支

模块没有 V1 mapping、但有 comfy_entrypoint 时:

  1. 检查 entrypoint callable;
  2. 同步或 await 调用;
  3. 验证返回 ComfyExtension;
  4. await extension.on_load();
  5. await extension.get_node_list();
  6. 验证得到 list;
  7. 对每个 class 调 GET_SCHEMA();
  8. 以 schema.node_id 注册,并同步 display name。

entrypoint、on_load 和 get_node_list 都允许异步,适合读取配置或轻量初始化。但启动过程会等待它们,不适合长时间远端请求。

为什么 V1 优先

源码用 if NODE_CLASS_MAPPINGS ... elif comfy_entrypoint。一个模块同时导出两者时只走 V1。迁移扩展时应明确选择入口,不要期待两套节点自动合并。

Web 扩展目录

模块可用两种方式声明前端资源:

  • pyproject.toml 的 Comfy 配置指定 web 目录和 project name;
  • 传统模块常量 WEB_DIRECTORY。

存在且为目录时,路径写入 EXTENSION_WEB_DIRS,PromptServer.add_routes() 再以扩展静态路径暴露给前端。

前端 JS 与 Python 节点同属受信任扩展:它在 ComfyUI 页面 origin 执行,可访问页面上下文与后端 API。部署审核不能只扫 .py,还要看 web assets。

prestartup 脚本

主启动链在 torch/comfy runtime 之前寻找并执行 prestartup_script.py,记录耗时与失败。

main.pyL171-225普通节点 import 之前的预启动代码执行。

适合的用途:

  • 调整扩展自己的 import path;
  • 检查/迁移扩展配置;
  • 与 Manager 协作准备依赖;
  • 做必须早于核心 import 的兼容设置。

不理想的用途:

  • 每次启动无条件在线安装未锁定依赖;
  • 修改全局 site-packages 而不记录;
  • 吞掉失败继续让节点以半安装状态导入;
  • 读取/上传用户数据。

加载失败如何降级

模块 import、V3 entrypoint 或 schema 失败时,加载器记录 traceback/warning 并返回 false。扫描完成后按耗时排序打印:

0.1 seconds: custom_nodes/fast_node 4.8 seconds (IMPORT FAILED): custom_nodes/broken_node

服务通常继续启动,成功节点仍可用。这适合桌面生态,却要求 UI/诊断明确展示缺失节点;不能只把“首页打开”当健康检查。

一个节点为什么没有出现:定位树

内建 extras 与外部 custom nodes

comfy_extras 也通过模块加载机制加入大量内建功能,但其路径和发布与核心仓库绑定。外部 custom nodes 来自用户目录,信任与兼容等级不同。

API/Partner nodes 位于 comfy_api_nodes,很多依赖 api.comfy.org 或第三方服务。它们可能标记 is_api_node、价格 badge,并要求敏感 API key。关闭 API nodes 时服务还会调整 CSP/路由能力。

API key 的边界

/prompt 会把 auth_token_comfy_org、api_key_comfy_org 等敏感 extra data 与普通历史字段拆开,执行前恢复。节点应通过受支持的 hidden/context 获取,不要:

  • 写进 UI output;
  • 放入图片 metadata;
  • 打进异常 traceback / repr;
  • 作为普通 cache signature 内容;
  • 传给无关第三方域名。

敏感字段分离只是减少意外传播,不代替网络访问控制和 secret rotation。

自定义节点的进程权限

一旦 import,节点可以:

  • 读写进程有权访问的文件;
  • 发起网络请求;
  • 读取环境变量与密钥;
  • 修改全局 Python 对象;
  • 占用 GPU/RAM;
  • 注册路由或前端脚本(视扩展机制而定)。

因此核心 schema 验证只验证 Prompt 输入,不隔离插件实现。真正需要多租户隔离时,应在 OS/container/process 边界运行不同信任级别的实例。

发布一个可靠扩展

建议最小结构:

custom_node_project/ ├── __init__.py V1 mapping 或 V3 entrypoint ├── nodes/ 业务节点 ├── pyproject.toml 元数据、依赖、可选 web 配置 ├── web/ 可选前端扩展 ├── tests/ schema、执行、cache、路径测试 ├── README.md 兼容版本与模型依赖 └── requirements/lock 可复现依赖策略

测试不只“能 import”

至少覆盖:

  1. node IDs 唯一且 schema finalize 成功;
  2. required/optional/hidden 与执行签名一致;
  3. 每个 output slot 类型和 tuple 数量正确;
  4. list mapping / batch 边界;
  5. fingerprint 在外部状态变更时变化;
  6. async 异常与取消不重复副作用;
  7. expand ID 唯一、无环、父缓存失效正确;
  8. 路径输入不能逃逸允许目录;
  9. Web assets 不泄露 token 或引入不必要远端脚本。

可迁移的设计方法

  • 插件发现先做 allow/deny policy,再 import;import 后已经拥有权限。
  • 核心扩展点保留 ID 命名空间,拒绝插件覆盖基础协议。
  • 插件初始化失败可降级,但必须提供结构化可观测性。
  • 后端插件和同源前端资源按同一信任等级审核。
  • 新旧插件协议共享注册表和执行器,避免生态一分为二。
Last updated on