Home

Awesome

Effortless Nodes for ComfyUI

This package aims to make adding new ComfyUI nodes as easy as possible, allowing you to write basic annotated Python and automatically turn it into a ComfyUI node definition via a simple @ComfyNode annotation.

For example:

from easy_nodes import ComfyNode, ImageTensor, MaskTensor, NumberInput

@ComfyNode(color="#0066cc", bg_color="#ffcc00", return_names=["Below", "Above"])
def threshold_image(image: ImageTensor,
                    threshold: float = NumberInput(0.5, 0, 1, 0.01, display="slider")) -> tuple[MaskTensor, MaskTensor]:
    """Returns separate masks for values above and below the threshold value."""
    mask_below = torch.any(image < threshold, dim=-1)
    return mask_below.float(), (~mask_below).float()

That (plus a tiny bit of initialization in __init__.py) and your node is ready for ComfyUI!

In addition, it provides enhanced node customization previously only available with custom JavaScript (e.g. color, and adding preview images/text), and several general ComfyUI quality of life improvements.

More examples can be found here.

Features

Core Functionality

Advanced Features

ComfyUI Quality of Life Improvements

New Features in Action

<img src="assets/threshold_example.png" alt="basic example" style="width: 100%;">New icons on node titlebars: Logs, Info, and Source.<br>Node colors set via @ComfyNode decorator.<img src="assets/log_streaming.png" alt="Log streaming" style="width: 100%;">Live log streaming. Just hover over the 📜 icon, and click the pin to make the window persistent.
<img src="assets/menu_options.png" alt="New menu options" style="width: 100%;">All options.<img src="assets/exceptions.png" alt="Better stack traces">Better stack traces. Set the stack trace prefix to get prettier dialogues with links directly to the source locations.

Changelog

New in 1.2:

New in 1.1:

New in 1.0:

Installation

To use this module in your ComfyUI project, follow these steps:

  1. Install the Module: Run the following command to install the ComfyUI-EasyNodes module:

    pip install ComfyUI-EasyNodes
    

    or, if you want to have an editable version:

    git clone https://github.com/andrewharp/ComfyUI-EasyNodes
    pip install -e ComfyUI-EasyNodes
    

    Note that this is not a typical ComfyUI nodepack, so does not itself live under custom_nodes.

    However, after installing you can copy the example node directory into custom_nodes to test them out:

    git clone --depth=1 https://github.com/andrewharp/ComfyUI-EasyNodes.git /tmp/easynodes
    mv /tmp/easynodes/example $COMFYUI_DIR/custom_nodes/easynodes
    
  2. Integrate into Your Project: In __init__.py:

    import easy_nodes
    easy_nodes.initialize_easy_nodes(default_category=my_category, auto_register=False)
    
    # This must come after calling initialize_easy_nodes.
    import your_node_module  # noqa: E402
    
    NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS = easy_nodes.get_node_mappings()
    
    # Export so that ComfyUI can pick them up.
    __all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
    
    # Optional: export the node list to a file so that e.g. ComfyUI-Manager can pick it up.
    easy_nodes.save_node_list(os.path.join(os.path.dirname(__file__), "node_list.json"))
    

    You can also initialize with auto_register=True, in which case you won't have to do anything else after the import. However, this may be problematic for having your nodes indexed so will default to False in a future update (currently not setting it explicitly will auto-register and complain).

Initialization options

The options passed to easy_nodes.initialize_easy_nodes will apply to all nodes registered until the next time easy_nodes.initialize_easy_nodes is called.

The settings mostly control defaults and some optional features that I find nice to have, but which may not work for everybody, so some are turned off by default.

Using the decorator

  1. Annotate Functions with @ComfyNode: Decorate your processing functions with @ComfyNode. The decorator accepts the following parameters:

    • category: Specifies the category under which the node will be listed in ComfyUI. Default is "ComfyNode".
    • display_name: Optionally specifies a human-readable name for the node as it will appear in ComfyUI. If not provided, a name is generated based on the function name.
    • workflow_name: The internal unique identifier for this node type. If not provided, a name is generated based on the function name.
    • description: An optional description for the node. If not provided the function's docstring, if any, will be used according to easy_nodes.docstring_mode.
    • is_output_node: Maps to ComfyUI's IS_OUTPUT_NODE.
    • return_types: Maps to ComfyUI's RETURN_TYPES. Use if the return type of the function itself is dynamic.
    • return_names: Maps to ComfyUI's RETURN_NAMES.
    • validate_inputs: Maps to ComfyUI's VALIDATE_INPUTS.
    • is_changed: Maps to ComfyUI's IS_CHANGED.
    • always_run: Makes the node always run by generating a random IS_CHANGED.
    • debug: A boolean that makes this node print out extra information during its lifecycle.
    • color: Changes the node's color.
    • bg_color: Changes the node's color. If color is set and not bg_color, bg_color will just be a slightly darker color.
    • width: Default width for this node type on creation.
    • height: Default height for this node type on creation.

    Example:

    from easy_nodes import ComfyNode, ImageTensor, NumberInput
    
    @ComfyNode(category="Image Processing",
               display_name="Enhance Image",
               is_output_node=True,
               debug=True,
               color="#FF00FF")
    def enhance_image(image: ImageTensor, factor: NumberInput(0.5, 0, 1, 0.1)) -> ImageTensor:
        output_image = enhance_my_image(image, factor)
        easy_nodes.show_image(output_image)  # Will show the image on the node, so you don't need a separate PreviewImage node.
        return output_image
    
  2. Annotate your function inputs and outputs: Fully annotate function parameters and return types, using list to wrap types as appropriate. tuple[output1, output2] should be used if you have multiple outputs, otherwise you can just return the naked type (in the example below, that would be list[int]). This information is used to generate the fields of the internal class definition @ComfyNode sends to ComfyUI. If you don't annotate the inputs, the input will be treated as a wildcard. If you don't annotate the output, you won't see anything at all in ComfyUI.

    Example:

    @ComfyNode("Utilities")
    def add_value(img_list: list[ImageTensor], val: int) -> list[int]:
        return [img + val for img in img_list]
    

Registering new types:

Say you want a new type of special Tensor that ComfyUI will treat differently from Images; perhaps a rotation matrix. Just create a placeholder class for it and use that in your annotations -- it's just for semantics; internally your functions will get whatever type of class they're handed (though with the verification settings turned on, you can still be assured it's a Tensor object (and you are free to create your own custom verifier for more control).

class RotationMatrix(torch.Tensor):
    def __init__(self):
        raise TypeError("!") # Will never be instantiated

easy_nodes.register_type(RotationMatrix, "ROTATION_MATRIX", verifier=TensorVerifier("ROTATION_MATRIX"))

@ComfyNode()
def rotate_matrix_more(rot1: RotationMatrix, rot2: RotationMatrix) -> RotationMatrix:
    return rot1 * rot2

Making the class extend a torch.Tensor is not necessary, but it will give you nice type hints in IDEs.

Creating dynamic nodes from classes

You can also automatically create nodes that will expose the fields of a class as widgets (as long as it has a default constructor). Say you have a complex options class from a third-party library you want to pass to a node.

from some_library import ComplexOptions

easy_nodes.register_type(ComplexOptions)

easy_nodes.create_field_setter_node(ComplexOptions)

Now you should be should find a node named ComplexOptions that will have all the basic field types (str, int, float, bool) exposed as widgets.

Automatic LLM Debugging

To enable the experimental LLM-based debugging, set your OPENAI_API_KEY prior to starting ComfyUI.

e.g.:

export OPENAI_API_KEY=sk-P#$@%J345jsd...
python main.py

Then open settings and turn the LLM debugging option to either "On" or "AutoFix".

Behavior:

This feature is very experimental, and any contributions for things like improving the prompt flow and suporting other LLMs are welcome! You can find the implementation in easy_nodes/llm_debugging.py.

Contributing

Contributions are welcome! Please submit pull requests or open issues for any bugs, features, or improvements.