Overview
The ComfyUI V3 schema introduces a more organized way of defining nodes, and future extensions to node features will only be added to V3 schema. You can use this guide to help you migrate your existing V1 nodes to the new V3 schema.Core Concepts
The V3 schema is kept on the new versioned Comfy API, meaning future revisions to the schema will be backwards compatible.comfy_api.latest will point to the latest numbered API that is still under development; the version before latest is what can be considered ‘stable’. Version v0_0_2 is the current (and first) API version so more changes will be made to it without warning. Once it is considered stable, a new version v0_0_3 will be created for latest to point at.
V1 vs V3 Architecture
The biggest changes in V3 schema are:- Inputs and Outputs defined by objects instead of a dictionary.
- The execution method is fixed to the name ‘execute’ and is a class method.
def comfy_entrypoint()function that returns a ComfyExtension object defines exposed nodes instead of NODE_CLASS_MAPPINGS/NODE_DISPLAY_NAME_MAPPINGS- Node objects do not expose ‘state’ -
def __init__(self)will have no effect on what is exposed in the node’s functions, as all of them are class methods. The node class is sanitized before execution as well.
V1 (Legacy)
V3 (Modern)
Migration Steps
Going from V1 to V3 should be simple in most cases and is simply a syntax change.Step 1: Change Base Class
All V3 Schema nodes should inherit fromComfyNode. Multiple layers of inheritance are okay as long as at the top of the chain there is a ComfyNode parent.
V1:
Step 2: Convert INPUT_TYPES to define_schema
Node properties like node id, display name, category, etc. that were assigned in different places in code such as dictionaries and class properties are now kept together via theSchema class.
The define_schema(cls) function is expected to return a Schema object in much the same way INPUT_TYPES(s) worked in V1.
Supported core Input/Output types are stored and documented in comfy_api/{version} in _io.py, which is namespaced as io by default. Since Inputs/Outputs are defined by classes now instead of dictionaries or strings, custom types are supported by either defining your own class or using the helper function Custom in io.
Custom types are elaborated on in a section further below.
A type class has the following properties:
class Inputfor Inputs (i.e.Model.Input(...))class Outputfor Outputs (i.e.Model.Output(...)). Note that all types may not support being an output.Typefor getting a typehint of the type (i.e.Model.Type). Note that some typehints are justany, which may be updated in the future. These typehints are not enforced and just act as useful documentation.
Step 3: Update Execute Method
All execution functions in v3 are namedexecute and are class methods.
V1:
Step 4: Convert Node Properties
Here are some examples of property names; see the source code incomfy_api.latest._io for more details.
| V1 Property | V3 Schema Field | Notes |
|---|---|---|
RETURN_TYPES | outputs in Schema | List of Output objects |
RETURN_NAMES | display_name in Output | Per-output display names |
FUNCTION | Always execute | Method name is standardized |
CATEGORY | category in Schema | String value |
OUTPUT_NODE | is_output_node in Schema | Boolean flag |
DEPRECATED | is_deprecated in Schema | Boolean flag |
EXPERIMENTAL | is_experimental in Schema | Boolean flag |
Step 5: Handle Special Methods
The same special methods are supported as in v1, but either lowercased or renamed entirely to be more clear. Their usage remains the same.Validation (V1 → V3)
The input validation function was renamed tovalidate_inputs.
V1:
Lazy Evaluation (V1 → V3)
Thecheck_lazy_status function is class method, remains the same otherwise.
V1:
Cache Control (V1 → V3)
The functionality of cache control remains the same as in V1, but the original name was very misleading as to how it operated. V1’sIS_CHANGED function signals execution not to trigger rerunning the node if the return value is the SAME as the last time the node was ran.
Thus, the function IS_CHANGED was renamed to fingerprint_inputs. One of the most common mistakes by developers was thinking if you return True, the node would always re-run. Because True would always be returned, it would have the opposite effect of only making the node run once and reuse cached values.
An example of using this function is the LoadImage node. It returns the hash of the selected file, so that if the file changes, the node will be forced to rerun.
V1:
Step 6: Create Extension and Entry Point
Instead of defining dictionaries to link node id to node class/display name, there is now aComfyExtension class and an expected comfy_entrypoint function to be defined.
In the future, more functions may be added to ComfyExtension to register more than just nodes via get_node_list.
comfy_entrypoint can be either async or not, but get_node_list must be defined as async.
V1:
Input Type Reference
Already explained in step 2, but here are some type reference comparisons in V1 vs V3. Seecomfy_api.latest._io for the full type declarations.
Basic Types
| V1 Type | V3 Type | Example |
|---|---|---|
"INT" | io.Int.Input() | io.Int.Input("count", default=1, min=0, max=100) |
"FLOAT" | io.Float.Input() | io.Float.Input("strength", default=1.0, min=0.0, max=10.0) |
"STRING" | io.String.Input() | io.String.Input("text", multiline=True) |
"BOOLEAN" | io.Boolean.Input() | io.Boolean.Input("enabled", default=True) |
control_after_generate
Int and Combo inputs support acontrol_after_generate parameter that adds a control widget for automatically changing the value after each generation. In V1 this was a plain bool; in V3 you can use the io.ControlAfterGenerate enum for explicit control. Passing True is equivalent to io.ControlAfterGenerate.randomize.
| Value | Behavior |
|---|---|
io.ControlAfterGenerate.fixed | Value stays the same after each generation. |
io.ControlAfterGenerate.increment | Value increments by the step after each generation. |
io.ControlAfterGenerate.decrement | Value decrements by the step after each generation. |
io.ControlAfterGenerate.randomize | Value is randomized after each generation. |
ComfyUI Types
| V1 Type | V3 Type | Example |
|---|---|---|
"IMAGE" | io.Image.Input() | io.Image.Input("image", tooltip="Input image") |
"MASK" | io.Mask.Input() | io.Mask.Input("mask", optional=True) |
"LATENT" | io.Latent.Input() | io.Latent.Input("latent") |
"CONDITIONING" | io.Conditioning.Input() | io.Conditioning.Input("positive") |
"MODEL" | io.Model.Input() | io.Model.Input("model") |
"VAE" | io.VAE.Input() | io.VAE.Input("vae") |
"CLIP" | io.CLIP.Input() | io.CLIP.Input("clip") |
Combo (Dropdowns/Selection Lists)
Combo types in V3 require explicit class definition. V1:Schema Reference
TheSchema dataclass defines all properties of a V3 node. Here is a complete reference of all available fields:
| Field | Type | Default | Description |
|---|---|---|---|
node_id | str | required | Globally unique ID of the node. Custom nodes should add a prefix/postfix to avoid clashes. |
display_name | str | None | Display name shown in the UI. Falls back to node_id if not set. |
category | str | "sd" | Category in the “Add Node” menu (e.g. "image/transform"). |
description | str | "" | Tooltip shown when hovering over the node. |
inputs | list[Input] | [] | List of input definitions. |
outputs | list[Output] | [] | List of output definitions. |
hidden | list[Hidden] | [] | List of hidden inputs to request (see Hidden Inputs). |
search_aliases | list[str] | [] | Alternative names for search. Useful for synonyms or old names after renaming. |
is_output_node | bool | False | Marks the node as an output node, causing it and its dependencies to be executed. |
is_input_list | bool | False | When True, all inputs become list[type] regardless of how many items are passed in. |
is_deprecated | bool | False | Flags the node as deprecated, signaling users to find alternatives. |
is_experimental | bool | False | Flags the node as experimental, warning users it may change. |
is_dev_only | bool | False | Hides the node from search/menus unless dev mode is enabled. |
is_api_node | bool | False | Flags the node as an API node for Comfy API services. |
not_idempotent | bool | False | When True, the node will always re-run and never reuse cached outputs from a different identical node in the graph. |
enable_expand | bool | False | Allows NodeOutput to include an expand property for node expansion. |
accept_all_inputs | bool | False | When True, all inputs from the prompt are passed as kwargs, even if not defined in the schema. |
Common Input Parameters
All input types share these base parameters:| Parameter | Type | Default | Description |
|---|---|---|---|
id | str | required | Unique identifier for the input, used as the kwarg name in execute. |
display_name | str | None | Label shown in the UI. Defaults to id. |
optional | bool | False | Whether the input is optional. |
tooltip | str | None | Hover tooltip text. |
lazy | bool | None | Marks input for lazy evaluation (see Lazy Evaluation). |
raw_link | bool | None | When True, passes the raw link info instead of the resolved value. |
advanced | bool | None | When True, the input is hidden behind an “Advanced” toggle in the UI. |
| Parameter | Type | Default | Description |
|---|---|---|---|
default | varies | None | Default value for the widget. |
socketless | bool | None | When True, hides the input socket (widget only, no incoming connections). |
force_input | bool | None | When True, forces the widget to display as a socket input instead. |
Advanced Features
Hidden Inputs
Hidden inputs provide access to execution context like the prompt metadata, node ID, and other internal values. They are not visible in the UI. In V1, hidden inputs were declared as a"hidden" key in INPUT_TYPES. In V3, they are declared via the hidden parameter on the Schema, and their values are accessed via cls.hidden.
V1:
| Hidden Enum | Description |
|---|---|
io.Hidden.unique_id | The unique identifier of the node, matching the id on the client side. |
io.Hidden.prompt | The complete prompt sent by the client. |
io.Hidden.extra_pnginfo | Dictionary copied into metadata of saved .png files. |
io.Hidden.dynprompt | Instance of DynamicPrompt that may mutate during execution. |
io.Hidden.auth_token_comfy_org | Token acquired from signing into a ComfyOrg account on the frontend. |
io.Hidden.api_key_comfy_org | API key generated by ComfyOrg, allows skipping frontend sign-in. |
Some hidden values are automatically added based on Schema flags. Output nodes (
is_output_node=True) automatically receive prompt and extra_pnginfo. API nodes (is_api_node=True) automatically receive auth tokens.UI Helpers
V3 provides built-in UI helpers in theui module to handle common patterns like previewing and saving files. Pass them to io.NodeOutput via the ui parameter.
Preview Helpers
Preview helpers save temporary files and return UI data for in-node display.Save Helpers
Save helpers provide methods for saving files to the output directory with proper metadata embedding. They are typically used in output nodes.Returning Raw UI Dicts
If you need to return UI data that doesn’t have a helper, you can pass a dict directly:Output Nodes
For nodes that produce side effects (like saving files). Same as in V1, marking a node as output will display arun play button in the node’s context window, allowing for partial execution of the graph.
Custom Types
Create custom input/output types either via class definition or theCustom helper function.
MultiType Inputs
MultiType allows an input to accept more than one type. This is useful when a node can operate on different data types through the same input slot.
If the first argument (id) is an instance of an Input class instead of a string, that input will be used to create a widget with its overridden values. Otherwise, the input is socket-only.
MatchType (Generic Type Matching)
MatchType creates type-linked inputs and outputs. When a user connects a specific type to a MatchType input, all other inputs and outputs sharing the same template automatically constrain to that type. This is how nodes like Switch and Create List work with any type.
Dynamic Inputs
V3 introduces dynamic input types that change the available inputs based on user interaction. There is no V1 equivalent for these features.Autogrow
Autogrow creates a variable number of inputs that automatically grow as the user connects more. There are two template types:
TemplatePrefix generates inputs with a numbered prefix (e.g. image0, image1, image2…):
DynamicCombo
DynamicCombo creates a dropdown that shows/hides different inputs depending on the selected option. This is useful for nodes where different modes require different parameters.
Async Execute
V3 supports asyncexecute methods. This is useful for nodes that perform I/O operations, API calls, or other async work. Simply declare execute as async:
ComfyAPI
TheComfyAPI class provides access to ComfyUI runtime services like progress reporting and node replacement registration. Import it and create an instance:
Progress Reporting
Report execution progress from within a node’sexecute method. The progress bar is displayed in the ComfyUI interface. This replaces the V1 pattern of using comfy.utils.PROGRESS_BAR_HOOK.
set_progress can accept a PIL Image, an ImageInput tensor, or None for the preview_image parameter. When called from within execute, the node_id is automatically determined from the executing context.Node Replacement
Node replacement allows mapping old/deprecated nodes to new ones, so existing workflows automatically upgrade. Register replacements using theComfyAPI in your extension’s on_load method.
old_widget_ids parameter is important: workflow JSON stores widget values by position index, not by name. This list maps those positional indexes to input IDs so the replacement system can correctly identify widget values during migration.
For nodes using dynamic inputs (like Autogrow), use dotted paths in the mapping:
Extension Lifecycle
TheComfyExtension class supports lifecycle hooks beyond just get_node_list:
NodeOutput
TheNodeOutput class is the standardized return value from execute. It supports several patterns: