comfyui-node-packaging

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

ComfyUI Custom Node Packaging

ComfyUI自定义节点打包指南

How to structure, register, and publish a custom node package.
如何构建、注册和发布自定义节点包。

Project Structure

项目结构

ComfyUI/custom_nodes/
  my_custom_nodes/
    __init__.py            # Entry point (required)
    nodes.py               # Node class definitions
    requirements.txt       # Python dependencies
    pyproject.toml         # Package metadata
    README.md              # Documentation
    js/                    # Frontend extensions (optional)
    │   └── my_extension.js
    docs/                  # Help pages (optional)
    │   └── MyNode.md
    locales/               # i18n translations (optional)
        └── zh/
            └── main.json
ComfyUI/custom_nodes/
  my_custom_nodes/
    __init__.py            # 入口文件(必填)
    nodes.py               # 节点类定义
    requirements.txt       # Python依赖
    pyproject.toml         # 包元数据
    README.md              # 文档
    js/                    # 前端扩展(可选)
    │   └── my_extension.js
    docs/                  # 帮助页面(可选)
    │   └── MyNode.md
    locales/               # 国际化翻译(可选)
        └── zh/
            └── main.json

Entry Point: init.py

入口文件:init.py

V3 Registration (Recommended)

V3版本注册(推荐)

python
undefined
python
undefined

init.py

init.py

from typing_extensions import override from comfy_api.latest import ComfyExtension, io
from .nodes import MyNode1, MyNode2, MyNode3
WEB_DIRECTORY = "./js" # optional: frontend JS extensions
class MyNodesExtension(ComfyExtension): @override async def get_node_list(self) -> list[type[io.ComfyNode]]: return [MyNode1, MyNode2, MyNode3]
@override
async def on_load(self):
    # Optional: run initialization logic when extension loads
    pass
async def comfy_entrypoint() -> MyNodesExtension: return MyNodesExtension()
undefined
from typing_extensions import override from comfy_api.latest import ComfyExtension, io
from .nodes import MyNode1, MyNode2, MyNode3
WEB_DIRECTORY = "./js" # 可选:前端JS扩展
class MyNodesExtension(ComfyExtension): @override async def get_node_list(self) -> list[type[io.ComfyNode]]: return [MyNode1, MyNode2, MyNode3]
@override
async def on_load(self):
    # 可选:扩展加载时运行初始化逻辑
    pass
async def comfy_entrypoint() -> MyNodesExtension: return MyNodesExtension()
undefined

V1 Registration (Legacy)

V1版本注册(旧版)

python
undefined
python
undefined

init.py

init.py

from .nodes import MyNode1, MyNode2
NODE_CLASS_MAPPINGS = { "MyNode1": MyNode1, "MyNode2": MyNode2, }
NODE_DISPLAY_NAME_MAPPINGS = { "MyNode1": "My Node 1", "MyNode2": "My Node 2", }
WEB_DIRECTORY = "./js"
all = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
undefined
from .nodes import MyNode1, MyNode2
NODE_CLASS_MAPPINGS = { "MyNode1": MyNode1, "MyNode2": MyNode2, }
NODE_DISPLAY_NAME_MAPPINGS = { "MyNode1": "My Node 1", "MyNode2": "My Node 2", }
WEB_DIRECTORY = "./js"
all = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
undefined

Node Definitions File

节点定义文件

python
undefined
python
undefined

nodes.py

nodes.py

import torch from comfy_api.latest import io
class MyNode1(io.ComfyNode): @classmethod def define_schema(cls): return io.Schema( node_id="MyNode1_UniqueID", # globally unique display_name="My Node 1", category="my_nodes", description="Does something useful", inputs=[ io.Image.Input("image"), io.Float.Input("value", default=1.0, min=0.0, max=10.0), ], outputs=[io.Image.Output("IMAGE")], )
@classmethod
def execute(cls, image, value):
    return io.NodeOutput(image * value)
undefined
import torch from comfy_api.latest import io
class MyNode1(io.ComfyNode): @classmethod def define_schema(cls): return io.Schema( node_id="MyNode1_UniqueID", # 全局唯一 display_name="My Node 1", category="my_nodes", description="Does something useful", inputs=[ io.Image.Input("image"), io.Float.Input("value", default=1.0, min=0.0, max=10.0), ], outputs=[io.Image.Output("IMAGE")], )
@classmethod
def execute(cls, image, value):
    return io.NodeOutput(image * value)
undefined

Dependencies: requirements.txt

依赖文件:requirements.txt

undefined
undefined

requirements.txt

requirements.txt

opencv-python>=4.8.0 requests>=2.28.0

**Important**: Only list dependencies not already included with ComfyUI. ComfyUI ships with: `torch`, `torchvision`, `torchaudio`, `numpy`, `PIL/Pillow`, `scipy`, `safetensors`, `transformers`, `accelerate`.
opencv-python>=4.8.0 requests>=2.28.0

**重要提示**:仅列出ComfyUI未自带的依赖。ComfyUI默认包含以下依赖:`torch`、`torchvision`、`torchaudio`、`numpy`、`PIL/Pillow`、`scipy`、`safetensors`、`transformers`、`accelerate`。

pyproject.toml

pyproject.toml

toml
[project]
name = "comfyui-my-nodes"
version = "1.0.0"
description = "My custom nodes for ComfyUI"
license = "MIT"
requires-python = ">=3.10"

[project.urls]
Repository = "https://github.com/username/comfyui-my-nodes"
toml
[project]
name = "comfyui-my-nodes"
version = "1.0.0"
description = "My custom nodes for ComfyUI"
license = "MIT"
requires-python = ">=3.10"

[project.urls]
Repository = "https://github.com/username/comfyui-my-nodes"

Frontend Extensions (JavaScript)

前端扩展(JavaScript)

Place
.js
files in the
WEB_DIRECTORY
:
my_custom_nodes/
  js/
    my_widgets.js      # Custom widget implementations
    my_extension.js    # Extension hooks
python
undefined
.js
文件放置在
WEB_DIRECTORY
目录下:
my_custom_nodes/
  js/
    my_widgets.js      # 自定义组件实现
    my_extension.js    # 扩展钩子
python
undefined

init.py

init.py

WEB_DIRECTORY = "./js"

All `.js` files in this directory are loaded by the frontend automatically. CSS and other resources can be accessed at `extensions/my_custom_nodes/filename.css`.
WEB_DIRECTORY = "./js"

该目录下所有`.js`文件会被前端自动加载。CSS及其他资源可通过`extensions/my_custom_nodes/filename.css`访问。

Help Pages

帮助页面

Create markdown documentation per node:
my_custom_nodes/
  docs/
    MyNode1.md         # filename matches node_id
markdown
<!-- docs/MyNode1.md -->
为每个节点创建Markdown文档:
my_custom_nodes/
  docs/
    MyNode1.md         # 文件名与node_id匹配
markdown
<!-- docs/MyNode1.md -->

My Node 1

My Node 1

Processes images with adjustable value.
使用可调节数值处理图像。

Inputs

输入

  • image: The input image
  • value: Processing strength (0.0 - 10.0)
  • image: 输入图像
  • value: 处理强度(0.0 - 10.0)

Outputs

输出

  • IMAGE: The processed image
undefined
  • IMAGE: 处理后的图像
undefined

Internationalization (i18n)

国际化(i18n)

my_custom_nodes/
  locales/
    zh/
      main.json
      nodeDefs.json    # node definition translations
json
// locales/zh/nodeDefs.json
{
    "MyNode1_UniqueID": {
        "display_name": "我的节点1",
        "description": "处理图像",
        "inputs": {
            "image": { "display_name": "图像" },
            "value": { "display_name": "数值", "tooltip": "处理强度" }
        }
    }
}
my_custom_nodes/
  locales/
    zh/
      main.json
      nodeDefs.json    # 节点定义翻译
json
// locales/zh/nodeDefs.json
{
    "MyNode1_UniqueID": {
        "display_name": "我的节点1",
        "description": "处理图像",
        "inputs": {
            "image": { "display_name": "图像" },
            "value": { "display_name": "数值", "tooltip": "处理强度" }
        }
    }
}

Single-File Node

单文件节点

For very simple nodes, everything can be in one file:
python
undefined
对于非常简单的节点,所有代码可放在一个文件中:
python
undefined

ComfyUI/custom_nodes/my_simple_node.py

ComfyUI/custom_nodes/my_simple_node.py

import torch from comfy_api.latest import ComfyExtension, io from typing_extensions import override
class InvertImage(io.ComfyNode): @classmethod def define_schema(cls): return io.Schema( node_id="SimpleInvert", display_name="Simple Invert", category="image", inputs=[io.Image.Input("image")], outputs=[io.Image.Output("IMAGE")], )
@classmethod
def execute(cls, image):
    return io.NodeOutput(1.0 - image)
class SimpleExtension(ComfyExtension): @override async def get_node_list(self): return [InvertImage]
async def comfy_entrypoint(): return SimpleExtension()
undefined
import torch from comfy_api.latest import ComfyExtension, io from typing_extensions import override
class InvertImage(io.ComfyNode): @classmethod def define_schema(cls): return io.Schema( node_id="SimpleInvert", display_name="Simple Invert", category="image", inputs=[io.Image.Input("image")], outputs=[io.Image.Output("IMAGE")], )
@classmethod
def execute(cls, image):
    return io.NodeOutput(1.0 - image)
class SimpleExtension(ComfyExtension): @override async def get_node_list(self): return [InvertImage]
async def comfy_entrypoint(): return SimpleExtension()
undefined

Key Imports

核心导入

python
undefined
python
undefined

V3 API core

V3 API核心

from comfy_api.latest import ComfyExtension, io, ui from comfy_api.latest import ComfyAPI # async runtime API (use with await) from comfy_api.latest import ComfyAPISync # sync runtime API (use in sync execute) from comfy_api.latest import Input # Input.Image, Input.Audio, Input.Mask, Input.Latent, Input.Video from comfy_api.latest import InputImpl # InputImpl.VideoFromFile, InputImpl.VideoFromComponents from comfy_api.latest import Types # Types.MESH, Types.VOXEL, Types.File3D, Types.VideoCodec from typing_extensions import override
from comfy_api.latest import ComfyExtension, io, ui from comfy_api.latest import ComfyAPI # 异步运行时API(需配合await使用) from comfy_api.latest import ComfyAPISync # 同步运行时API(用于同步execute) from comfy_api.latest import Input # Input.Image, Input.Audio, Input.Mask, Input.Latent, Input.Video from comfy_api.latest import InputImpl # InputImpl.VideoFromFile, InputImpl.VideoFromComponents from comfy_api.latest import Types # Types.MESH, Types.VOXEL, Types.File3D, Types.VideoCodec from typing_extensions import override

Common utilities

常用工具

import folder_paths # directory management from server import PromptServer # server-to-client messaging from comfy_execution.graph_utils import GraphBuilder # node expansion
undefined
import folder_paths # 目录管理 from server import PromptServer # 服务端到客户端消息传递 from comfy_execution.graph_utils import GraphBuilder # 节点扩展
undefined

Using folder_paths

使用folder_paths

ComfyUI provides
folder_paths
for accessing standard directories:
python
import folder_paths
ComfyUI提供
folder_paths
用于访问标准目录:
python
import folder_paths

Standard directories

标准目录

input_dir = folder_paths.get_input_directory() output_dir = folder_paths.get_output_directory() temp_dir = folder_paths.get_temp_directory()
input_dir = folder_paths.get_input_directory() output_dir = folder_paths.get_output_directory() temp_dir = folder_paths.get_temp_directory()

Model directories

模型目录

checkpoint_paths = folder_paths.get_folder_paths("checkpoints") lora_paths = folder_paths.get_folder_paths("loras")
checkpoint_paths = folder_paths.get_folder_paths("checkpoints") lora_paths = folder_paths.get_folder_paths("loras")

Register custom model folder

注册自定义模型文件夹

folder_paths.add_model_folder_path("my_models", "/path/to/models")
folder_paths.add_model_folder_path("my_models", "/path/to/models")

Get model file list

获取模型文件列表

models = folder_paths.get_filename_list("checkpoints")
undefined
models = folder_paths.get_filename_list("checkpoints")
undefined

Publishing to ComfyUI Registry

发布到ComfyUI注册表

1. Create
pyproject.toml

1. 创建
pyproject.toml

toml
[project]
name = "comfyui-my-nodes"
version = "1.0.0"
description = "My custom nodes"
license = "MIT"

[tool.comfy]
PublisherId = "your-publisher-id"
toml
[project]
name = "comfyui-my-nodes"
version = "1.0.0"
description = "My custom nodes"
license = "MIT"

[tool.comfy]
PublisherId = "your-publisher-id"

2. Publish

2. 发布

bash
comfy node publish
bash
comfy node publish

CI/CD with GitHub Actions

使用GitHub Actions实现CI/CD

yaml
undefined
yaml
undefined

.github/workflows/publish.yml

.github/workflows/publish.yml

name: Publish to ComfyUI Registry on: push: tags: - 'v*' jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.12' - run: pip install comfy-cli - run: comfy node publish env: COMFY_API_KEY: ${{ secrets.COMFY_API_KEY }}
undefined
name: Publish to ComfyUI Registry on: push: tags: - 'v*' jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.12' - run: pip install comfy-cli - run: comfy node publish env: COMFY_API_KEY: ${{ secrets.COMFY_API_KEY }}
undefined

Scaffolding with comfy-cli

使用comfy-cli搭建项目脚手架

bash
undefined
bash
undefined

Install comfy-cli

安装comfy-cli

pip install comfy-cli
pip install comfy-cli

Create new custom node project

创建新的自定义节点项目

cd ComfyUI/custom_nodes comfy node scaffold

This generates the boilerplate structure with all necessary files.
cd ComfyUI/custom_nodes comfy node scaffold

该命令会生成包含所有必要文件的项目模板。

Node ID Best Practices

Node ID最佳实践

  • Use a globally unique prefix:
    "MyProject_NodeName"
    or
    "username.NodeName"
  • Never change
    node_id
    after release (breaks saved workflows)
  • Use
    display_name
    for user-facing name changes
  • Use
    search_aliases
    for discoverability:
    search_aliases=["alias1", "alias2"]
  • 使用全局唯一前缀:
    "MyProject_NodeName"
    "username.NodeName"
  • 发布后切勿修改
    node_id
    (会破坏已保存的工作流)
  • 使用
    display_name
    修改面向用户的名称
  • 使用
    search_aliases
    提升可发现性:
    search_aliases=["alias1", "alias2"]

Common Patterns

常见模式

Organizing Multiple Node Files

多节点文件组织

python
undefined
python
undefined

init.py

init.py

from typing_extensions import override from comfy_api.latest import ComfyExtension, io
from .image_nodes import BlurNode, SharpenNode, ResizeNode from .text_nodes import ConcatNode, FormatNode from .util_nodes import SwitchNode, DebugNode
class MyExtension(ComfyExtension): @override async def get_node_list(self): return [ BlurNode, SharpenNode, ResizeNode, ConcatNode, FormatNode, SwitchNode, DebugNode, ]
async def comfy_entrypoint(): return MyExtension()
undefined
from typing_extensions import override from comfy_api.latest import ComfyExtension, io
from .image_nodes import BlurNode, SharpenNode, ResizeNode from .text_nodes import ConcatNode, FormatNode from .util_nodes import SwitchNode, DebugNode
class MyExtension(ComfyExtension): @override async def get_node_list(self): return [ BlurNode, SharpenNode, ResizeNode, ConcatNode, FormatNode, SwitchNode, DebugNode, ]
async def comfy_entrypoint(): return MyExtension()
undefined

Conditional Node Loading

条件加载节点

python
class MyExtension(ComfyExtension):
    @override
    async def get_node_list(self):
        nodes = [BasicNode]
        try:
            import cv2
            from .opencv_nodes import OpenCVNode
            nodes.append(OpenCVNode)
        except ImportError:
            pass
        return nodes
python
class MyExtension(ComfyExtension):
    @override
    async def get_node_list(self):
        nodes = [BasicNode]
        try:
            import cv2
            from .opencv_nodes import OpenCVNode
            nodes.append(OpenCVNode)
        except ImportError:
            pass
        return nodes

See Also

另请参阅

  • comfyui-node-basics
    - Node class structure
  • comfyui-node-frontend
    - JavaScript extension details
  • comfyui-node-migration
    - V1 to V3 migration
  • comfyui-node-basics
    - 节点类结构
  • comfyui-node-frontend
    - JavaScript扩展详情
  • comfyui-node-migration
    - V1到V3迁移指南