How to Create Custom Nodes with INPUT_TYPES and RETURN_TYPES in ComfyUI

ComfyUI custom nodes expose their interface through INPUT_TYPES and RETURN_TYPES class members (or the newer define_schema method), which the server reads in server.py to build the node catalog that the frontend renders.

Creating custom nodes in ComfyUI requires defining a Python class that declares its inputs and outputs so the node graph editor can render the correct sockets and type tags. According to the Comfy-Org/ComfyUI source code, the framework supports two distinct APIs for this metadata declaration: the classic tuple-based approach used by built-in nodes in nodes.py, and the modern schema-based API introduced for extensions. Both methods ultimately populate the JSON node definitions that server.py serves to the web interface.

The Classic API: INPUT_TYPES and RETURN_TYPES

Most built-in nodes in the ComfyUI repository use the classic interface definition style. This approach relies on two class-level members that the server inspects when building the node list.

Defining Inputs with INPUT_TYPES

The INPUT_TYPES class method returns a dictionary describing required and optional sockets, their data types, and UI rendering hints. In nodes.py, the CLIPTextEncode node demonstrates this pattern:

class CLIPTextEncode(ComfyNodeABC):
    @classmethod
    def INPUT_TYPES(s) -> dict:
        return {
            "required": {
                "text": (IO.STRING, {"multiline": True, "dynamicPrompts": True}),
                "clip": (IO.CLIP, {"tooltip": "The CLIP model used for encoding the text."})
            }
        }

Each key under "required" or "optional" maps to a tuple where:

  • The first element is a type tag (e.g., IO.STRING, IO.CLIP, IO.IMAGE)
  • The second element is a dictionary of UI hints such as multiline, tooltip, default, min, max, or lazy

When the server constructs the node catalog in server.py (around lines 54-66), it calls obj_class.INPUT_TYPES() to populate the input field of the node definition JSON.

Declaring Outputs with RETURN_TYPES

The RETURN_TYPES class attribute is a tuple of type tags that tells the engine what data types the node produces. This tuple must match the order of values returned by the execution function:

RETURN_TYPES = (IO.CONDITIONING,)
OUTPUT_TOOLTIPS = ("A conditioning containing the embedded text.",)
FUNCTION = "encode"
CATEGORY = "conditioning"

The FUNCTION attribute specifies which method receives the input values, and CATEGORY determines where the node appears in the UI menu. The server reads RETURN_TYPES directly from the class object to build the output array in the node definition.

The Modern Schema API with define_schema

Newer extensions can use the define_schema class method to declare inputs and outputs through strongly-typed io.Schema objects. This approach eliminates the need for separate INPUT_TYPES and RETURN_TYPES declarations by encapsulating all metadata in a single schema definition.

As implemented in custom_nodes/example_node.py.example, the schema API provides explicit input and output constructors:

from comfy_api.latest import io

class Example(io.ComfyNode):
    @classmethod
    def define_schema(cls) -> io.Schema:
        return io.Schema(
            node_id="Example",
            display_name="Example Node",
            category="Example",
            inputs=[
                io.Image.Input("image"),
                io.Int.Input(
                    "int_field",
                    min=0,
                    max=4096,
                    step=64,
                    display_mode=io.NumberDisplay.number,
                    lazy=True,
                ),
                io.Combo.Input("print_to_screen", options=["enable", "disable"]),
                io.String.Input(
                    "string_field",
                    default="Hello world!",
                    multiline=False,
                    lazy=True,
                ),
            ],
            outputs=[io.Image.Output()],
        )

The server prefers define_schema when present, falling back to INPUT_TYPES and RETURN_TYPES only when the schema method is absent. This API is defined in the comfy_api.latest.io module and supports advanced features like lazy evaluation and precise UI control.

Registering Nodes with ComfyExtension

Regardless of which API you choose, custom nodes must be exposed through a ComfyExtension subclass that the framework discovers at startup. The extension returns a list of node classes via the get_node_list method:

from comfy_api.latest import ComfyExtension, io

class ExampleExtension(ComfyExtension):
    async def get_node_list(self) -> list[type[io.ComfyNode]]:
        return [Example]

async def comfy_entrypoint():
    return ExampleExtension()

Place your extension files in the custom_nodes/ directory (or install via pip), and ComfyUI will automatically import them and extract the node definitions when the server builds its catalog.

Complete Implementation Examples

Classic API Implementation

This example mirrors the pattern found in nodes.py for a utility node that repeats text:


# custom_nodes/hello_node.py

from comfy_api.latest import io, ComfyNode

class HelloWorld(io.ComfyNode):
    @classmethod
    def INPUT_TYPES(cls):
        return {
            "required": {
                "text": (io.String.Input, {"default": "Hello", "multiline": False}),
                "repeat": (io.Int.Input, {"default": 1, "min": 1, "max": 10})
            }
        }

    RETURN_TYPES = ("STRING",)
    FUNCTION = "run"
    CATEGORY = "utils"

    @classmethod
    def run(cls, text, repeat):
        return ((" ".join([text] * repeat),))

Register it with an extension class in custom_nodes/__init__.py:

from comfy_api.latest import ComfyExtension
from .hello_node import HelloWorld

class HelloExtension(ComfyExtension):
    async def get_node_list(self):
        return [HelloWorld]

async def comfy_entrypoint():
    return HelloExtension()

Schema API Implementation

For new extensions, the schema approach provides better type safety and cleaner syntax:


# custom_nodes/greeter_node.py

from comfy_api.latest import io, ComfyExtension

class Greeter(io.ComfyNode):
    @classmethod
    def define_schema(cls) -> io.Schema:
        return io.Schema(
            node_id="Greeter",
            display_name="Greeter",
            category="utils",
            inputs=[
                io.String.Input("name", default="World"),
                io.Int.Input("exclamation", default=1, min=0, max=5)
            ],
            outputs=[io.String.Output()],
        )

    @classmethod
    def execute(cls, name, exclamation):
        return io.NodeOutput(name + "!" * exclamation)

class GreeterExtension(ComfyExtension):
    async def get_node_list(self):
        return [Greeter]

async def comfy_entrypoint():
    return GreeterExtension()

Summary

  • INPUT_TYPES is a class method returning a dictionary with "required" and "optional" keys that define input sockets, type tags, and UI hints.
  • RETURN_TYPES is a class attribute tuple that declares output types and must match the return order of the execution function.
  • The schema API using define_schema offers a modern alternative that encapsulates inputs and outputs in an io.Schema object, removing the need for separate INPUT_TYPES and RETURN_TYPES declarations.
  • The ComfyExtension class registers your nodes with the framework, exposing them through the get_node_list method.
  • Source files in custom_nodes/ are auto-imported at startup, with metadata extracted in server.py to build the node catalog JSON.

Frequently Asked Questions

What is the difference between INPUT_TYPES and define_schema?

INPUT_TYPES is the classic class method returning a dictionary structure that has been used since early ComfyUI versions, while define_schema is a newer method returning an io.Schema object that provides stronger typing and more explicit UI control. The server checks for define_schema first and falls back to INPUT_TYPES and RETURN_TYPES if the schema method is not present.

Where does ComfyUI read the node definitions from?

The server builds the node catalog in server.py (specifically in the node_info collection logic around lines 54-66), where it iterates over node classes and calls INPUT_TYPES() or reads RETURN_TYPES directly. This generates the JSON that the frontend consumes to render the node graph interface.

Can I mix classic and schema APIs in the same extension?

Yes, but not within the same node class. Individual node classes must use one approach or the other—either implementing INPUT_TYPES and RETURN_TYPES or overriding define_schema. The extension registration mechanism via ComfyExtension handles both types uniformly through the get_node_list method.

What file structure is required for custom nodes?

Place your Python files containing node classes inside the custom_nodes/ directory at the repository root. Include an extension class that implements comfy_entrypoint() returning a ComfyExtension instance. The framework automatically discovers and imports these files at server startup, extracting node definitions to populate the UI.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →