comfyui-node-packaging
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseComfyUI 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.jsonComfyUI/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.jsonEntry Point: init.py
入口文件:init.py
V3 Registration (Recommended)
V3版本注册(推荐)
python
undefinedpython
undefinedinit.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
passasync def comfy_entrypoint() -> MyNodesExtension:
return MyNodesExtension()
undefinedfrom 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):
# 可选:扩展加载时运行初始化逻辑
passasync def comfy_entrypoint() -> MyNodesExtension:
return MyNodesExtension()
undefinedV1 Registration (Legacy)
V1版本注册(旧版)
python
undefinedpython
undefinedinit.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"]
undefinedfrom .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"]
undefinedNode Definitions File
节点定义文件
python
undefinedpython
undefinednodes.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)undefinedimport 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)undefinedDependencies: requirements.txt
依赖文件:requirements.txt
undefinedundefinedrequirements.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 files in the :
.jsWEB_DIRECTORYmy_custom_nodes/
js/
my_widgets.js # Custom widget implementations
my_extension.js # Extension hookspython
undefined将文件放置在目录下:
.jsWEB_DIRECTORYmy_custom_nodes/
js/
my_widgets.js # 自定义组件实现
my_extension.js # 扩展钩子python
undefinedinit.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_idmarkdown
<!-- 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: 处理后的图像
undefinedInternationalization (i18n)
国际化(i18n)
my_custom_nodes/
locales/
zh/
main.json
nodeDefs.json # node definition translationsjson
// 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
undefinedComfyUI/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()
undefinedimport 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()
undefinedKey Imports
核心导入
python
undefinedpython
undefinedV3 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
undefinedimport folder_paths # 目录管理
from server import PromptServer # 服务端到客户端消息传递
from comfy_execution.graph_utils import GraphBuilder # 节点扩展
undefinedUsing folder_paths
使用folder_paths
ComfyUI provides for accessing standard directories:
folder_pathspython
import folder_pathsComfyUI提供用于访问标准目录:
folder_pathspython
import folder_pathsStandard 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")
undefinedmodels = folder_paths.get_filename_list("checkpoints")
undefinedPublishing to ComfyUI Registry
发布到ComfyUI注册表
1. Create pyproject.toml
pyproject.toml1. 创建pyproject.toml
pyproject.tomltoml
[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 publishbash
comfy node publishCI/CD with GitHub Actions
使用GitHub Actions实现CI/CD
yaml
undefinedyaml
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 }}
undefinedname: 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 }}
undefinedScaffolding with comfy-cli
使用comfy-cli搭建项目脚手架
bash
undefinedbash
undefinedInstall 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: or
"MyProject_NodeName""username.NodeName" - Never change after release (breaks saved workflows)
node_id - Use for user-facing name changes
display_name - Use for discoverability:
search_aliasessearch_aliases=["alias1", "alias2"]
- 使用全局唯一前缀:或
"MyProject_NodeName""username.NodeName" - 发布后切勿修改(会破坏已保存的工作流)
node_id - 使用修改面向用户的名称
display_name - 使用提升可发现性:
search_aliasessearch_aliases=["alias1", "alias2"]
Common Patterns
常见模式
Organizing Multiple Node Files
多节点文件组织
python
undefinedpython
undefinedinit.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()
undefinedfrom 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()
undefinedConditional 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 nodespython
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 nodesSee Also
另请参阅
- - Node class structure
comfyui-node-basics - - JavaScript extension details
comfyui-node-frontend - - V1 to V3 migration
comfyui-node-migration
- - 节点类结构
comfyui-node-basics - - JavaScript扩展详情
comfyui-node-frontend - - V1到V3迁移指南
comfyui-node-migration