About comfyui-node-basics
An introduction to the ComfyUI V3 API for node developers, explaining node classes, schemas, execution methods, and registration. It also outlines the V1 structure and key differences between the two APIs.
Key features
- 01V3 node class structure
- 02Schema input and output configuration
- 03ComfyExtension registration
- 04V1 and V3 structure comparison
Use cases
- 01Write a first V3 custom node
- 02Define node inputs, outputs, and categories
- 03Register nodes with ComfyUI
- 04Understand the legacy V1 node structure
Install
Copy the command and run it in a terminal.
$
npx skills add https:/ / github.com/ jtydhr88/ comfyui-custom-node-skills --skill comfyui-node-basicsAfter installing, type/comfyui-node-basicsin Claude Code to confirm it loaded.
Details
From the project READMEComfyUI Custom Node Basics (V3 API)
ComfyUI uses Python classes to define nodes. The V3 API is the current recommended approach. Nodes inherit from io.ComfyNode and define a schema + execute method.
Quick Start
from comfy_api.latest import ComfyExtension, io
class MyNode(io.ComfyNode):
@classmethod
def define_schema(cls):
return io.Schema(
node_id="MyNode",
display_name="My Custom Node",
category="my_category",
inputs=[
io.Image.Input("image"),
io.Float.Input("strength", default=1.0, min=0.0, max=1.0, step=0.01),
],
outputs=[
io.Image.Output("IMAGE"),
],
)
@classmethod
def execute(cls, image, strength):
result = image * strength
return io.NodeOutput(result)
V3 Node Class Structure
Every V3 node requires:
- Inherit from
io.ComfyNode define_schema(cls)- classmethod returningio.Schemaexecute(cls, ...)- classmethod performing the computation
from typing_extensions import override
from comfy_api.latest import ComfyExtension, io
class ImageBrighten(io.ComfyNode):
@classmethod
def define_schema(cls):
return io.Schema(
node_id="ImageBrighten", # unique identifier
display_name="Brighten Image", # shown in UI
category="image/adjust", # menu path
description="Adjusts image brightness",
inputs=[
io.Image.Input("image"),
io.Float.Input("factor", default=1.2, min=0.0, max=3.0, step=0.1),
],
outputs=[
io.Image.Output("IMAGE"),
],
)
@classmethod
def execute(cls, image, factor):
result = torch.clamp(image * factor, 0.0, 1.0)
return io.NodeOutput(result)
io.Schema Fields
io.Schema(
node_id="UniqueNodeID", # required: unique string ID
display_name="Display Name", # optional: shown in UI menus
category="category/subcategory", # menu hierarchy (default "sd")
description="Node description", # optional: tooltip text
inputs=[...], # list of Input objects
outputs=[...], # list of Output objects
hidden=[...], # list of Hidden enum values
is_output_node=False, # True for nodes with side effects (save, preview)
is_experimental=False, # marks as experimental
is_deprecated=False, # marks as deprecated
is_dev_only=False, # hidden unless dev mode enabled
is_api_node=False, # marks as API-only node
is_input_list=False, # receive full lists instead of individual items
not_idempotent=False, # no cache sharing between identical instances (see lifecycle)
accept_all_inputs=False, # accept arbitrary inputs via **kwargs
enable_expand=False, # allow node expansion (subgraphs)
search_aliases=["alias1", "alias2"],# alternative search terms
essentials_category="Basic", # optional: Essentials tab category
price_badge=None, # optional: PriceBadge for API nodes
has_intermediate_output=False, # True for nodes with interactive UI that produce intermediate outputs
loop_boundary=None, # "start"/"end": core loop validation (v0.36+, experimental)
)
V3 Node Registration
V3 nodes are registered via ComfyExtension and comfy_entrypoint():
from typing_extensions import override
from comfy_api.latest import ComfyExtension, io
class MyNode(io.ComfyNode):
@classmethod
def define_schema(cls):
return io.Schema(
node_id="MyNode",
display_name="My Node",
category="my_nodes",
inputs=[io.String.Input("text", multiline=True)],
outputs=[io.String.Output()],
)
@classmethod
def execute(cls, text):
return io.NodeOutput(text.upper())
class MyExtension(ComfyExtension):
@override
async def get_node_list(self) -> list[type[io.ComfyNode]]:
return [MyNode]
async def comfy_entrypoint() -> MyExtension:
return MyExtension()
The comfy_entrypoint() function must be defined at the module level (in the file directly imported by ComfyUI).
V1 Node Structure (Legacy Reference)
V1 nodes use class attributes and NODE_CLASS_MAPPINGS:
class MyNodeV1:
CATEGORY = "my_category"
FUNCTION = "execute"
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE",),
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0}),
}
}
def execute(self, image, strength):
return (image * strength,)
NODE_CLASS_MAPPINGS = {"MyNodeV1": MyNodeV1}
NODE_DISPLAY_NAME_MAPPINGS = {"MyNodeV1": "My Node V1"}
Key Differences: V3 vs V1
| Aspect | V3 | V1 |
|---|---|---|
| Base class | io.ComfyNode | Plain class |
| Execute method | execute classmethod (fixed name) | Instance method (custom name via FUNCTION) |
| Inputs | io.Schema(inputs=[...]) | INPUT_TYPES() dict |
| Outputs | io.Schema(outputs=[...]) | RETURN_TYPES tuple |
| Return value | io.NodeOutput(...) | Plain tuple |
| Registration | ComfyExtension + comfy_entrypoint() | NODE_CLASS_MAPPINGS dict |
| State | No instance state (classmethods) | Instance state allowed |
| Hidden inputs | cls.hidden.prompt, etc. | kwargs from "hidden" dict |
Important Rules
node_idmust be globally unique across all nodesexecute()parameters must match input IDs exactly- All methods are
@classmethodin V3 (no instance state) - Return
io.NodeOutput(val1, val2, ...)matching output count - Category uses
/separator for hierarchy:"image/transform" - Prefix category with
_to hide from menus:"_for_testing"
See Also
comfyui-node-datatypes- Data types (IMAGE, LATENT, MASK, etc.)comfyui-node-inputs- Input configuration detailscomfyui-node-outputs- Output types and UI outputscomfyui-node-packaging- Project structure and packagingcomfyui-node-lifecycle- Execution lifecycle and caching
Comments
Markdown supported