# How to Create Custom Nodes with INPUT_TYPES and RETURN_TYPES in ComfyUI

> Learn to create custom nodes in ComfyUI using INPUT_TYPES and RETURN_TYPES. Understand how to define your node's interface for seamless integration and rendering in the frontend.

- Repository: [Comfy Org/ComfyUI](https://github.com/Comfy-Org/ComfyUI)
- Tags: how-to-guide
- Published: 2026-02-26

---

**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`](https://github.com/Comfy-Org/ComfyUI/blob/main/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`](https://github.com/Comfy-Org/ComfyUI/blob/main/nodes.py), and the modern schema-based API introduced for extensions. Both methods ultimately populate the JSON node definitions that [`server.py`](https://github.com/Comfy-Org/ComfyUI/blob/main/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`](https://github.com/Comfy-Org/ComfyUI/blob/main/nodes.py), the `CLIPTextEncode` node demonstrates this pattern:

```python
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`](https://github.com/Comfy-Org/ComfyUI/blob/main/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:

```python
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:

```python
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:

```python
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`](https://github.com/Comfy-Org/ComfyUI/blob/main/nodes.py) for a utility node that repeats text:

```python

# 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`](https://github.com/Comfy-Org/ComfyUI/blob/main/custom_nodes/__init__.py):

```python
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:

```python

# 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`](https://github.com/Comfy-Org/ComfyUI/blob/main/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`](https://github.com/Comfy-Org/ComfyUI/blob/main/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.