Skill v1.0.1
currentAutomated scan100/100+3 new
version: "1.0.1" name: comfyui-node-registry description: Authoring & publishing ComfyUI custom nodes to the Comfy Registry — node structure, pyproject.toml spec, comfy-cli publishing, and CI
Authoring & Publishing ComfyUI Custom Nodes
This skill covers writing a ComfyUI custom node pack and publishing it to the Comfy Registry (registry.comfy.org), the public catalog that powers ComfyUI-Manager. For using existing nodes in workflows, see the comfyui-core skill instead.
Minimal Node Pack Structure
A node pack is a Python package placed under ComfyUI/custom_nodes/<name>/. The package __init__.py must export NODE_CLASS_MAPPINGS and NODE_DISPLAY_NAME_MAPPINGS; WEB_DIRECTORY is optional (only if the pack ships frontend JS).
ComfyUI/custom_nodes/my-node-pack/├── __init__.py # exports the mappings ComfyUI scans for├── nodes.py # node class definitions├── pyproject.toml # registry metadata (required to publish)├── requirements.txt # optional Python deps├── .comfyignore # optional — exclude files from the published archive├── LICENSE├── README.md└── web/js/ # optional frontend extension (see WEB_DIRECTORY)
__init__.py
from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS# Optional: serve frontend JS/CSS from this folder (path relative to __init__.py)WEB_DIRECTORY = "./web/js"__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
A minimal node class (nodes.py)
class ImageSelector:CATEGORY = "example" # menu path where the node appears@classmethoddef INPUT_TYPES(cls):return {"required": {"images": ("IMAGE",),"mode": (["brightest", "reddest", "greenest", "bluest"],),},"optional": {"threshold": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),"count": ("INT", {"default": 1, "min": 1, "max": 64}),"label": ("STRING", {"default": "", "multiline": False}),},}RETURN_TYPES = ("IMAGE",) # tuple of output data typesRETURN_NAMES = ("image",) # optional friendly output namesFUNCTION = "choose_image" # name of the method ComfyUI callsOUTPUT_NODE = False # True for terminal nodes (e.g. SaveImage)def choose_image(self, images, mode, threshold=0.5, count=1, label=""):import torchbrightness = [torch.mean(img.flatten()).item() for img in images]best = brightness.index(max(brightness))return (images[best].unsqueeze(0),) # MUST return a tupleNODE_CLASS_MAPPINGS = {"ImageSelector": ImageSelector, # globally unique class_type key}NODE_DISPLAY_NAME_MAPPINGS = {"ImageSelector": "Image Selector", # label shown in the UI}
Node class contract
| Member | Required | Purpose | |
|---|---|---|---|
INPUT_TYPES | yes | @classmethod returning {"required": {...}, "optional": {...}}. Each input is (TYPE,) or (TYPE, {opts}). | |
RETURN_TYPES | yes | Tuple of output type strings (e.g. ("IMAGE", "MASK")). Single output still needs a trailing comma. | |
FUNCTION | yes | String name of the method to execute. | |
CATEGORY | yes | Menu path string for the Add Node menu. | |
RETURN_NAMES | no | Friendly names for outputs (defaults to lowercased types). | |
OUTPUT_NODE | no | True marks a terminal node that produces a result (save/preview). |
Input type widgets — the options dict drives the UI widget:
("INT", {"default": 0, "min": 0, "max": 100, "step": 1})("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.1})("STRING", {"default": "", "multiline": True})(["a", "b", "c"],)— a literal list becomes a dropdown("IMAGE",),("LATENT",),("MODEL",)etc. — typed connections (no widget)
The executing method must return a tuple matching RETURN_TYPES, even for a single output (return (result,)).
pyproject.toml — Registry Metadata
Required to publish. comfy node init scaffolds this file. See references/pyproject.toml for a fully-commented example.
[project]name = "my-node-pack" # unique & IMMUTABLE; lowercase, <100 chars, no "ComfyUI" prefixdescription = "What this node pack does"version = "1.0.0" # semantic version X.Y.Z — bump to publish a new versionlicense = { file = "LICENSE" } # or { text = "MIT License" }requires-python = ">=3.10"dependencies = ["comfyui-frontend-package<=1.21.6", # optional — pin frontend if you ship UI]classifiers = ["Operating System :: OS Independent",][project.urls]Repository = "https://github.com/you/my-node-pack" # REQUIRED — must be a valid repo URLDocumentation = "https://github.com/you/my-node-pack/wiki""Bug Tracker" = "https://github.com/you/my-node-pack/issues"[tool.comfy]PublisherId = "your-publisher-id" # the id after @ on your registry profileDisplayName = "My Node Pack" # human-friendly name in the registryIcon = "https://raw.githubusercontent.com/you/my-node-pack/main/icon.png" # square, max 400x400pxrequires-comfyui = ">=1.0.0" # optional ComfyUI version constraint
Field rules (verified against the live spec)
| Field | Section | Notes | |
|---|---|---|---|
name | [project] | Unique and immutable once published. <100 chars; alphanumeric + - _ .; no consecutive special chars; can't start with a number/special char; case-insensitive. Don't prefix with "ComfyUI". | |
version | [project] | Semantic X.Y.Z — X breaking, Y backwards-compatible feature, Z bug fix. Each value is published once and is immutable. | |
description | [project] | Short summary (recommended). | |
license | [project] | { file = "LICENSE" } or { text = "MIT License" }. | |
requires-python | [project] | e.g. ">=3.10" (recommended). | |
dependencies | [project] | PEP 508 requirement strings; can pin comfyui-frontend-package. | |
Repository | [project.urls] | Required — valid GitHub repo URL. | |
PublisherId | [tool.comfy] | Required — your publisher id (after the @ on your profile). | |
DisplayName | [tool.comfy] | Friendly registry name (optional). | |
Icon | [tool.comfy] | URL to a square image, max 400×400px; SVG/PNG/JPG/GIF. | |
Banner | [tool.comfy] | URL to a 21:9 banner image; SVG/PNG/JPG/GIF (optional). | |
requires-comfyui | [tool.comfy] | ComfyUI version range using < > <= >= ~= != (optional). | |
includes | [tool.comfy] | Array forcing extra folders into the published archive. |
Controlling the published archive
- `.comfyignore` — uses
.gitignoresyntax; files listed are excluded from the published archive. Use it to drop tests, examples, large assets, and dev files. - `[tool.comfy].includes` — the inverse: force-includes folders that would otherwise be skipped (e.g. a bundled
web/dist).
Registry Setup (one-time)
- Go to registry.comfy.org and create a publisher.
- Your Publisher ID is the value after the
@on your profile page. It is globally unique and cannot be changed — use it forPublisherIdinpyproject.toml. - In the publisher's section, create an API key. Name it and save it somewhere safe — if you lose it you must create a new one (it is not recoverable).
CLI Publishing Flow
Install comfy-cli (requires Python 3.10+; a virtualenv is recommended):
pip install comfy-cli
| Command | What it does | |
|---|---|---|
comfy node init | Scaffolds pyproject.toml with registry metadata in the current node pack folder. Fill in the required fields (esp. PublisherId and Repository). | |
comfy node publish | Validates and uploads the current version to the registry. Prompts for your API key. Prints the registry URL on success. |
Version immutability: once a version is published it cannot be modified or overwritten. To ship changes, bump version and publish again. To pull a bad version, deprecate it on the website (More Actions > Deprecate), which prompts users to upgrade rather than deleting it.
CI Publishing (GitHub Actions)
Automate publishing on every version bump. Add the API key as a repo secret named `REGISTRY_ACCESS_TOKEN` (Settings > Secrets and variables > Actions), then create .github/workflows/publish_action.yml:
name: Publish to Comfy registryon:workflow_dispatch:push:branches:- mainpaths:- "pyproject.toml"jobs:publish-node:name: Publish Custom Node to registryruns-on: ubuntu-lateststeps:- name: Check out codeuses: actions/checkout@v4- name: Publish Custom Nodeuses: Comfy-Org/publish-node-action@mainwith:personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
- Triggers on push to `main` but only when `pyproject.toml` changes (i.e. when you bump
version).workflow_dispatchallows manual runs. - If your default branch isn't
main, update thebranches:list. - The action reads the version from
pyproject.tomland publishes it — so the typical flow is: bumpversion, commit, push tomain, done.
Optional Frontend Extension
If your pack adds custom UI (widgets, sidebar tabs, menu items), set WEB_DIRECTORY in __init__.py and ship JS there. New frontend extensions should target the modern `@comfyorg/extension-api` rather than poking at legacy globals; pull in @comfyorg/comfyui-frontend-types for TypeScript types (npm install -D @comfyorg/comfyui-frontend-types). For the full frontend authoring workflow (defineExtension/defineNode/defineWidget and the defineSidebarTab/defineCommand/defineSetting shell APIs), see the sibling `comfyui-frontend-extensions` skill.
Common Mistakes
- Forgetting the return tuple — the
FUNCTIONmethod mustreturn (value,), notreturn value, even for one output. - Single-element `RETURN_TYPES` without a comma —
("IMAGE")is a string, not a tuple. Write("IMAGE",). - `INPUT_TYPES` not a `@classmethod` — ComfyUI calls it on the class; missing the decorator breaks node loading.
- Trying to overwrite a published version — versions are immutable. Bump
versioninstead; deprecate bad ones on the website. - Renaming `name` after publishing — it's immutable and globally unique. Pick a good name (no "ComfyUI" prefix) up front.
- Missing `[project.urls].Repository` — it's required; publishing fails without a valid repo URL.
- Wrong `PublisherId` — use the id after the
@on your profile, not your display name. - Oversized icon — must be square and ≤ 400×400px; larger images are rejected.
- Renaming class keys in `NODE_CLASS_MAPPINGS` — the key is the
class_typestored in workflow JSON. Changing it breaks every saved workflow that used the node. - Committing the API key — store it as the
REGISTRY_ACCESS_TOKENsecret; never inpyproject.tomlor the repo.
Sources
- Official: Comfy Registry at https://registry.comfy.org and comfy-cli publishing docs used by
comfy node publish. - Empirical: field-rule notes verified against the live spec; common-mistakes list is from observed publish failures.