A growing collection of fragments of example code…

Comfy UI preference settings

Add and read a setting

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

/* In setup(), add the setting */
    app.ui.settings.addSetting({
        id: "unique.setting.name",
        name: "Switch my cool extension on?",
        type: "boolean", // "text" is another simple option
        defaultValue: false,
        /* To listen for changes, add an onChange parameter
        onChange: (newVal, oldVal) => { console.log("Setting got changed!") },
        */
    });

/* then elsewhere, read it (with a default value just in case) */
    if (app.ui.settings.getSettingValue("unique.setting.name", false)) { 
        /* do something */
    }

Sliders for numbers

The type slider lets the user enter a value directly or via a slider:

    app.ui.settings.addSetting({
        id: "unique.setting.slider",
        name: "Move me around",
        type: "slider",
        attrs: { min: -1, max: 500, step: 1, },
        defaultValue: 0,
        onChange: (newVal, oldVal) => { console.log(`Setting got changed to ${newVal}`) },
    });

Right click menus

Background menu

The main background menu (right-click on the canvas) is generated by a call to
LGraph.getCanvasMenuOptions. One way to add your own menu options is to hijack this call:

/* in setup() */
    const original_getCanvasMenuOptions = LGraphCanvas.prototype.getCanvasMenuOptions;
    LGraphCanvas.prototype.getCanvasMenuOptions = function () {
        // get the basic options 
        const options = original_getCanvasMenuOptions.apply(this, arguments);
        options.push(null); // inserts a divider
        options.push({
            content: "The text for the menu",
            callback: async () => {
                // do whatever
            }
        })
        return options;
    }

Node menu

When you right click on a node, the menu is similarly generated by node.getExtraMenuOptions. But instead of returning an options object, this one gets it passed in…

/* in beforeRegisterNodeDef() */
if (nodeType?.comfyClass=="MyNodeClass") { 
    const original_getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
    nodeType.prototype.getExtraMenuOptions = function(_, options) {
        original_getExtraMenuOptions?.apply(this, arguments);
        options.push({
            content: "Do something fun",
            callback: async () => {
                // fun thing
            }
        })
    }   
}

If you want a submenu, provide a callback which uses LiteGraph.ContextMenu to create it:

function make_submenu(value, options, e, menu, node) {
    const submenu = new LiteGraph.ContextMenu(
        ["option 1", "option 2", "option 3"],
        { 
            event: e, 
            callback: function (v) { 
                // do something with v (=="option x")
            }, 
            parentMenu: menu, 
            node:node
        }
    )
}

/* ... */
    options.push(
        {
            content: "Menu with options",
            has_submenu: true,
            callback: make_submenu,
        }
    )

Capture UI events

This works just like you’d expect - find the UI element in the DOM and add an eventListener. setup() is a good place to do this, since the page has fully loaded. For instance, to detect a click on the ‘Queue’ button:

function queue_button_pressed() { console.log("Queue button was pressed!") }
document.getElementById("queue-button").addEventListener("click", queue_button_pressed);

Detect when a workflow starts

This is one of many api events:

import { api } from "../../scripts/api.js";
/* in setup() */
    function on_execution_start() { 
        /* do whatever */
    }
    api.addEventListener("execution_start", on_execution_start);

Detect an interrupted workflow

A simple example of hijacking the api:

import { api } from "../../scripts/api.js";
/* in setup() */
    const original_api_interrupt = api.interrupt;
    api.interrupt = function () {
        /* Do something before the original method is called */
        original_api_interrupt.apply(this, arguments);
        /* Or after */
    }

Catch clicks on your node

node has a mouseDown method you can hijack. This time we’re careful to pass on any return value.

async nodeCreated(node) {
    if (node?.comfyClass === "My Node Name") {
        const original_onMouseDown = node.onMouseDown;
        node.onMouseDown = function( e, pos, canvas ) {
            alert("ouch!");
            return original_onMouseDown?.apply(this, arguments);
        }        
    }
}