TL;DR:"I want to create a custom ComfyUI node for the desktop version. It must include JavaScript that runs inside the ComfyUI front-end when the UI loads. The node folder must contain a /web/js/ directory with the JavaScript file inside. In __init__.py, you must define WEB_DIRECTORY = './web/js' and include WEB_DIRECTORY in the __all__ list, so that ComfyUI will load the JavaScript into the UI. Also add a clear console.log() in the .js file to verify that it's loaded. The final node should work in the local ComfyUI desktop version without additional setup."


Example Output Expected
========================

*Directory layout:

MyCustomNode/
├── __init__.py
├── src/
│   └── MyCustomNode/
│       ├── __init__.py
│       └── nodes.py
├── other stuff
└── web/
    └── js/
        └── my_custom_script.js

*Inside __init__.py:

from .my_node import NODE_CLASS_MAPPINGS

WEB_DIRECTORY = "./web/js"
__all__ = ['NODE_CLASS_MAPPINGS', 'WEB_DIRECTORY']

*Inside my_custom_script.js:

console.log("[MyCustomNode] ✅ JavaScript loaded successfully.");

=======================

before attempting to create the nodes check the repo of another working nodes like :

>> https://github.com/yolain/ComfyUI-Easy-Use/tree/main
>> https://github.com/jags111/efficiency-nodes-comfyui/tree/main
>> https://github.com/M1kep/ComfyLiterals/tree/master

IMPORTANT: at start before going deeper, create the directories with simple nodes to check if the nodes are working and JavaScript .js extensions are loaded successfully.

function and behaviors of the nodes :

1- Dynamic Sliders Stack node
- has "sliders_count" widget to control how many sliders visible (type=int, min=1, step=1, max=10, default=5)
- has "sliders_Max" widget to control the Maximum value of each slider (type=float, min=0, step=0.01, max=2, default=1)
- has title and slider widget pairs.
- the title widget is directly on top of it's corresponding slider widget and it allows to type in the title of the slider below it.
- the sliders widgets are colored bars that allow for dragging to adjust the slider value, it should contain a displayed name similar to the title (type= float, min=0, step=0.01, max=sliders_Max, default= 1)
- increasing sliders_count by 1, adds a new title-slider pair and the opposite is true. The node shape and appearance updates based on the number of widgets visible. the node should not have extra empty space, or lacks the space for the displayed widgets. this behaviour is similar how the Lora_stack works as in the efficiency_nodes. the node should be manually sized without any issue.
- the Sliders_Max value is the cap value for all sliders. for example if slider_1 has the maximum value compared to the rest of sliders and is set to 2, and I adjusted the sliders_max value to 0.5, the Slider_1 value should dynamically move to 0.5 and update it's value in the UI live. also, the rest of sliders shall decrease while maintaining the ratios between them. sliders_Max is like a scaling variable that scale the whole sliders such that the slider with maximum value is set to the sliders_Max value and the rest of sliders follow while keeping the ratio between them. also, the Sliders_Max value shall equal to the value of the slider with maximum value all the time (caped at 2).
- silder_(i) = slider_normalized_(i) * slider_Max << this uses normalized values so that the sliders values are maintained proportionally. 
- the node has one output (type=string) it has the values of the displayed sliders only as a series e.g. (0.01, 0.50, 1.00, ... ). 



2- Stack Receiver node
- it has one input port (labeled: Input) , and one input widget (Labelled= Slider Index).
- the input port accepts the output of the Dynamic Sliders Stack node.
- the input widget set the index of the Slider (index start from 1 to 10 )
- the node has one output port that gives the value of the Indexed Slider.



general requirements to be considered:
- I want a complete working, error_free Custom node folder, with all the necessary .js , .py , .text, etc.. files.
- It must stick to comfyui custom nodes standard folder structure and file naming.
- javascript is allowed.
- the two node must be in different .py files.
- consistency between nodes in terms of variables names and such.
- in comfyui UI I should be able to right click and navigate to add node >> custom nodes >> Dynamic_Slider_Pack and find my two nodes. 
- I'm using comfyui desktop app 
- the path to comfyui : D:\ComfyUi
- utilize the use of loops to make adjustment easier.




///references: 

///example custom node file :

class Example:
    """
    A example node

    Class methods
    -------------
    INPUT_TYPES (dict):
        Tell the main program input parameters of nodes.
    IS_CHANGED:
        optional method to control when the node is re executed.

    Attributes
    ----------
    RETURN_TYPES (`tuple`):
        The type of each element in the output tuple.
    RETURN_NAMES (`tuple`):
        Optional: The name of each output in the output tuple.
    FUNCTION (`str`):
        The name of the entry-point method. For example, if `FUNCTION = "execute"` then it will run Example().execute()
    OUTPUT_NODE ([`bool`]):
        If this node is an output node that outputs a result/image from the graph. The SaveImage node is an example.
        The backend iterates on these output nodes and tries to execute all their parents if their parent graph is properly connected.
        Assumed to be False if not present.
    CATEGORY (`str`):
        The category the node should appear in the UI.
    DEPRECATED (`bool`):
        Indicates whether the node is deprecated. Deprecated nodes are hidden by default in the UI, but remain
        functional in existing workflows that use them.
    EXPERIMENTAL (`bool`):
        Indicates whether the node is experimental. Experimental nodes are marked as such in the UI and may be subject to
        significant changes or removal in future versions. Use with caution in production workflows.
    execute(s) -> tuple || None:
        The entry point method. The name of this method must be the same as the value of property `FUNCTION`.
        For example, if `FUNCTION = "execute"` then this method's name must be `execute`, if `FUNCTION = "foo"` then it must be `foo`.
    """
    def __init__(self):
        pass

    @classmethod
    def INPUT_TYPES(s):
        """
            Return a dictionary which contains config for all input fields.
            Some types (string): "MODEL", "VAE", "CLIP", "CONDITIONING", "LATENT", "IMAGE", "INT", "STRING", "FLOAT".
            Input types "INT", "STRING" or "FLOAT" are special values for fields on the node.
            The type can be a list for selection.

            Returns: `dict`:
                - Key input_fields_group (`string`): Can be either required, hidden or optional. A node class must have property `required`
                - Value input_fields (`dict`): Contains input fields config:
                    * Key field_name (`string`): Name of a entry-point method's argument
                    * Value field_config (`tuple`):
                        + First value is a string indicate the type of field or a list for selection.
                        + Second value is a config for type "INT", "STRING" or "FLOAT".
        """
        return {
            "required": {
                "image": ("IMAGE",),
                "int_field": ("INT", {
                    "default": 0, 
                    "min": 0, #Minimum value
                    "max": 4096, #Maximum value
                    "step": 64, #Slider's step
                    "display": "number", # Cosmetic only: display as "number" or "slider"
                    "lazy": True # Will only be evaluated if check_lazy_status requires it
                }),
                "float_field": ("FLOAT", {
                    "default": 1.0,
                    "min": 0.0,
                    "max": 10.0,
                    "step": 0.01,
                    "round": 0.001, #The value representing the precision to round to, will be set to the step value by default. Can be set to False to disable rounding.
                    "display": "number",
                    "lazy": True
                }),
                "print_to_screen": (["enable", "disable"],),
                "string_field": ("STRING", {
                    "multiline": False, #True if you want the field to look like the one on the ClipTextEncode node
                    "default": "Hello World!",
                    "lazy": True
                }),
            },
        }

    RETURN_TYPES = ("IMAGE",)
    #RETURN_NAMES = ("image_output_name",)

    FUNCTION = "test"

    #OUTPUT_NODE = False

    CATEGORY = "Example"

    def check_lazy_status(self, image, string_field, int_field, float_field, print_to_screen):
        """
            Return a list of input names that need to be evaluated.

            This function will be called if there are any lazy inputs which have not yet been
            evaluated. As long as you return at least one field which has not yet been evaluated
            (and more exist), this function will be called again once the value of the requested
            field is available.

            Any evaluated inputs will be passed as arguments to this function. Any unevaluated
            inputs will have the value None.
        """
        if print_to_screen == "enable":
            return ["int_field", "float_field", "string_field"]
        else:
            return []

    def test(self, image, string_field, int_field, float_field, print_to_screen):
        if print_to_screen == "enable":
            print(f"""Your input contains:
                string_field aka input text: {string_field}
                int_field: {int_field}
                float_field: {float_field}
            """)
        #do some processing on the image, in this example I just invert it
        image = 1.0 - image
        return (image,)

    """
        The node will always be re executed if any of the inputs change but
        this method can be used to force the node to execute again even when the inputs don't change.
        You can make this node return a number or a string. This value will be compared to the one returned the last time the node was
        executed, if it is different the node will be executed again.
        This method is used in the core repo for the LoadImage node where they return the image hash as a string, if the image hash
        changes between executions the LoadImage node is executed again.
    """
    #@classmethod
    #def IS_CHANGED(s, image, string_field, int_field, float_field, print_to_screen):
    #    return ""

# Set the web directory, any .js file in that directory will be loaded by the frontend as a frontend extension
# WEB_DIRECTORY = "./somejs"


# Add custom API routes, using router
from aiohttp import web
from server import PromptServer

@PromptServer.instance.routes.get("/hello")
async def get_hello(request):
    return web.json_response("hello")


# A dictionary that contains all nodes you want to export with their names
# NOTE: names should be globally unique
NODE_CLASS_MAPPINGS = {
    "Example": Example
}

# A dictionary that contains the friendly/humanly readable titles for the nodes
NODE_DISPLAY_NAME_MAPPINGS = {
    "Example": "Example Node"
}

/// reddit page :
https://www.reddit.com/r/comfyui/comments/18wp6oj/tutorial_create_a_custom_node_in_5_minutes/?rdt=32812

/// typical Directory Structure and templates files :

my_custom_nodepack/             *Name folder of the project.
├── .editorconfig               *Configuration file to define and maintain consistent coding styles between different editors and IDEs.
├── .gitignore                  *File specifying files and directories to be ignored by Git.
├── .pre-commit-config.yaml     *Configuration file for pre-commit hooks, used to automate checks before commits.
├── .vscode/                    *Folder containing configurations specific to Visual Studio Code.
│   └── settings.json           *Configuration file for workspace settings in VS Code.
├── LICENSE                     *File containing the project's license
├── MANIFEST.in                 *File used to include additional files in the package during distribution.
├── README.md                   *Main documentation file for the project, often used to describe the project, how to install and use it
├── pyproject.toml              *File containing project metadata and dependencies.  
├── src/                        *Folder containing the source code of the project.
│   └── my_custom_nodepack/     *Folder containing the source code of the custom Node.
│       ├── __init__.py         *File to make Python treat the directory as a package.
│       └── nodes.py            *File containing the custom Node class. 
├── tests/                      *Folder containing test files.
│   ├── __init__.py             *File to make Python treat the directory as a package.
│   ├── conftest.py             *File containing fixtures for the tests.
│   ├── pytest.ini              *Configuration file for pytest.
│   └── test_my_custom_nodepack.py  *File containing tests for the custom Node.
├── web/                        *Folder containing web assets.
│   └── js/                     *Folder containing JavaScript files.
│       └── example.js          *JavaScript file.
└── .github/                    *Folder containing GitHub-specific files.
    ├── ISSUE_TEMPLATE.md       *Template for creating issues.
    └── workflows/              *Folder containing GitHub Actions workflows.
        ├── build-pipeline.yml  *Workflow file for the build pipeline.
        ├── publish_node.yml    *Workflow file for publishing the custom Node.
        └── validate.yml        *Workflow file for validating the custom Node.


ComfyUI JavaScript Injection Guide (Prompt-Ready Format)
========================================================

TL;DR (AI-Optimized)
---------------------
I want to create a custom ComfyUI node for the desktop version. It must include JavaScript
that runs inside the ComfyUI front-end automatically when UI loads.

✅ Place the JS in `/web/js/`
✅ Declare `WEB_DIRECTORY = './web/js'` in `__init__.py`
✅ JavaScript must use `app.registerExtension()` wrapped in a `setInterval` until `app` is available.


Directory Layout
----------------
MyCustomNode/
├── __init__.py               # maps node + JS
├── nodes/
│   └── my_node.py            # Python logic of your node
└── web/
    └── js/
        └── my_script.js      # Front-end logic (UI behaviors, etc.)


__init__.py Template
--------------------
from .nodes.my_node import MyNode

NODE_CLASS_MAPPINGS = {
    "MyNode": MyNode
}

NODE_DISPLAY_NAME_MAPPINGS = {
    "MyNode": "My Custom Node"
}

WEB_DIRECTORY = "./web/js"  # 👈 Key line to inject JS into UI

__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY']


JavaScript Template (Guaranteed to Work)
----------------------------------------
(function () {
  console.log("[MyNode] ✅ Injecting via UI event hook");

  const extension = {
    name: "MyNode",
    async beforeRegisterNodeDef(nodeType, nodeData, app) {
      if (nodeData.name !== "MyNode") return;

      nodeType.prototype.onNodeCreated = function () {
        const node = this;
        // 🔁 Customize widget logic here
        console.log("[MyNode] UI logic running...");
      };
    }
  };

  // 🔁 Poll until ComfyUI's `app` is available
  const waitForApp = setInterval(() => {
    if (typeof app !== 'undefined') {
      app.registerExtension(extension);
      clearInterval(waitForApp);
    }
  }, 100);
})();


Quick Test for Success
----------------------
When ComfyUI loads:
- Open browser devtools
- You should see:
  [MyNode] ✅ Injecting via UI event hook


References
----------
- ComfyLiterals:       https://github.com/M1kep/ComfyLiterals
- ComfyUI-Easy-Use:    https://github.com/yolain/ComfyUI-Easy-Use
- Efficiency Nodes:    https://github.com/jags111/efficiency-nodes-comfyui

Just rename your node logic and identifiers. Works for any custom node JS.


