Skip to content
Development
Skill

/comfyui-node-frontend

ComfyUI frontend JavaScript extensions - hooks, widgets, sidebar tabs, commands, settings, toasts, dialogs. Use when adding UI features to custom nodes, creating custom widgets, or extending the ComfyUI frontend.

From plugin
comfyui-custom-node-skills
2659 skills
Install
$ npx -y skills add jtydhr88/comfyui-custom-node-skills --skill comfyui-node-frontend --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/comfyui-node-frontend

Context preview

The summary Claude sees to decide when to auto-load this skill.

ComfyUI frontend JavaScript extensions - hooks, widgets, sidebar tabs, commands, settings, toasts, dialogs. Use when adding UI features to custom nodes, creating custom widgets, or extending the ComfyUI frontend.

SKILL.md

comfyui-node-frontend.SKILL.md
name: comfyui-node-frontend
description: ComfyUI frontend JavaScript extensions - hooks, widgets, sidebar tabs, commands, settings, toasts, dialogs. Use when adding UI features to custom nodes, creating custom widgets, or extending the ComfyUI frontend.

ComfyUI Frontend Extensions

Custom nodes can extend the ComfyUI frontend with JavaScript. Extensions register hooks, widgets, commands, settings, and UI components.

Quick Start

1. Export WEB_DIRECTORY in Python

# __init__.py
WEB_DIRECTORY = "./js"
__all__ = ["WEB_DIRECTORY"]

2. Create JavaScript Extension

// js/my_extension.js
import { app } from "../../scripts/app.js";

app.registerExtension({
    name: "my_nodes.my_extension",

    async setup() {
        console.log("Extension loaded!");
    },
});

All `.js` files in `WEB_DIRECTORY` are loaded automatically when ComfyUI starts.

Extension Hooks (Lifecycle Order)

init — After canvas created, before nodes

app.registerExtension({
    name: "my.ext",
    async init(app) {
        // Modify core behavior, add global listeners
    },
});

addCustomNodeDefs — Modify node definitions

async addCustomNodeDefs(defs, app) {
    // defs is a dict of all node definitions
    // Can add or modify definitions before registration
    defs["MyFrontendNode"] = {
        input: { required: { text: ["STRING", {}] } },
        output: ["STRING"],
        output_name: ["text"],
        name: "MyFrontendNode",
        display_name: "My Frontend Node",
        category: "custom",
    };
},

getCustomWidgets — Register custom widget types

getCustomWidgets(app) {
    return {
        MY_WIDGET(node, inputName, inputData, app) {
            const widget = node.addWidget("text", inputName, "", () => {});
            widget.serializeValue = () => widget.value;
            return { widget };
        },
    };
},

beforeRegisterNodeDef — Modify node prototype

async beforeRegisterNodeDef(nodeType, nodeData, app) {
    if (nodeData.name === "MyNode") {
        // Chain onto prototype methods
        const origOnCreated = nodeType.prototype.onNodeCreated;
        nodeType.prototype.onNodeCreated = function () {
            origOnCreated?.apply(this, arguments);
            // Add custom widget, modify behavior, etc.
            this.addWidget("button", "Run", null, () => {
                console.log("Button clicked!");
            });
        };
    }
},

nodeCreated — After node instance created

nodeCreated(node, app) {
    if (node.comfyClass === "MyNode") {
        // Modify this specific node instance
        node.color = "#335";
    }
},

setup — After app fully loaded

async setup(app) {
    // Add event listeners, register UI components
    app.api.addEventListener("executed", (event) => {
        console.log("Node executed:", event.detail);
    });
},

loadedGraphNode — When loading saved graph

loadedGraphNode(node, app) {
    if (node.comfyClass === "MyNode") {
        // Restore state from saved graph
    }
},

registerCustomNodes — Register additional node types

registerCustomNodes(app) {
    // Register custom LiteGraph node types
},

beforeRegisterVueAppNodeDefs — Modify node defs before Vue registration

beforeRegisterVueAppNodeDefs(defs, app) {
    // Modify definitions before they reach the Vue app
},

beforeConfigureGraph / afterConfigureGraph

async beforeConfigureGraph(graphData, missingNodeTypes, app) {
    // Before graph data is applied
},
async afterConfigureGraph(missingNodeTypes, app) {
    // After graph is fully configured
},

getSelectionToolboxCommands — Add commands to selection toolbox

getSelectionToolboxCommands(selectedItem) {
    // Return array of command IDs to show when item is selected
    return ["my.ext.doSomething"];
},

onNodeOutputsUpdated — Execution outputs changed

onNodeOutputsUpdated(nodeOutputs) {
    // nodeOutputs: Record<NodeLocatorId, output data>, fired when node outputs update
},

Authentication Hooks

onAuthUserResolved(user, app) {
    // Fires when user authentication resolves
},
onAuthTokenRefreshed() {
    // Fires when auth token is refreshed
},
onAuthUserLogout() {
    // Fires when user logs out
},

Custom Widgets

Adding DOM Widgets

beforeRegisterNodeDef(nodeType, nodeData, app) {
    if (nodeData.name === "MyNode") {
        const origOnCreated = nodeType.prototype.onNodeCreated;
        nodeType.prototype.onNodeCreated = function () {
            origOnCreated?.apply(this, arguments);

            const container = document.createElement("div");
            container.innerHTML = `<input type="color" value="#ff0000">`;
            container.querySelector("input").addEventListener("change", (e) => {
                this.widgets.find(w => w.name === "color").value = e.target.value;
            });

            this.addDOMWidget("colorPicker", "custom", container, {
                serialize: true,
                getValue() { return container.querySelector("input").value; },
                setValue(v) { container.querySelector("input").value = v; },
            });
        };
    }
},

Widget Hooks

// Called before prompt is queued
// isPartialExecution is true when only selected nodes are queued
widget.beforeQueued = function ({ isPartialExecution } = {}) {
    // Prepare widget value
};

// Called after prompt is queued
widget.afterQueued = function ({ isPartialExecution } = {}) {
    // Reset or update widget (e.g. control_after_generate)
};

// Custom serialization
widget.serializeValue = function (node, index) {
    return JSON.stringify(this.value);
};

Declarative Extension Properties

Commands

app.registerExtension({
    name:
Read more
Ships withcomfyui-custom-node-skills

A curated collection of agent skills (for Claude Code and OpenAI Codex) for developing ComfyUI custom nodes. These skills give the agent comprehensive knowledge of the ComfyUI node system, covering both the V3 (recommended) and V1 (legacy) APIs.

Get the whole plugin