Skip to main content
This tutorial adds a live seed badge and a “Reset seed” menu item to the built-in KSampler node. It demonstrates module registration, definition hooks, node and widget handles, a user-visible mutation, and lifecycle timing without touching a frontend global or prototype.

1. Create the pack layout

This tutorial changes the V2 implementation under v2/. A converted pack keeps its V1 distribution at the top level and repeats the complete distribution under v2/; the V2 tree is a replacement pack root, not an overlay. Expose the V2 web directory from v2/__init__.py:
ComfyUI discovers JavaScript modules in that directory when it loads the V2 custom-node package. Create the module at v2/web/seed-tools.js.

2. Import the API

Create v2/web/seed-tools.js:
The published module is installed before custom-node modules evaluate. Import it directly; do not import /scripts/app.js, read window.comfyAPI, or wait for a DOM element. require() makes the dependency explicit. If the host is too old, the error names the missing capability and the API version. For an optional enhancement, use supports() and skip only that enhancement instead.

3. Extend KSampler

Register the definition behavior at module scope:
There is no constructor and no prototype patch:
  • defs.extend() selects a registered node type;
  • onCreated() receives an ID-backed NodeHandle after the node joins a graph;
  • node.widgets.get() finds the seed by stable name rather than array position;
  • setValue() commits through the same value protocol as a user edit;
  • addBadge() asks the host to render node chrome under either renderer.
The badge callback is evaluated when the node is drawn, so it reads the latest seed. Keep a dynamic badge callback fast. For expensive work, update cached pack state from a widget change listener and have the badge read that state.

4. Add feedback after execution

The definition builder can observe backend results without subscribing to raw backend messages or guessing which node produced them:
Multiple extensions of the same type compose. They do not need to capture and call a previous callback. In a real pack, keep related hooks in one defs.extend() call when that makes the behavior easier to read.

5. Use application readiness only when needed

Definition registration belongs at module scope. Work that needs the initialized graph belongs behind onReady:
onReady fires once. If the extension must repeat work after every workflow open, use onWorkflowLoaded instead. The returned function unsubscribes. A module-lifetime listener normally lives as long as the page; a listener owned by a tab, dialog, widget, or node should be released when that owner is destroyed.

6. Make an optional feature degrade cleanly

Suppose a later version anchors a panel to the node’s screen rectangle:
The capability check describes the needed behavior. Do not compare frontend application versions or test whether an internal property happens to exist.

7. Verify behavior

Exercise at least these cases in a real frontend:
  1. create a fresh KSampler and confirm the badge appears;
  2. change the seed manually and confirm the badge follows it;
  3. choose “Reset seed” and confirm linked seed controls and serialization react as they would to a user edit;
  4. duplicate, save, reload, and delete the node;
  5. run the workflow and confirm the notification is attributed to the correct node;
  6. repeat under every renderer the pack claims to support.
Static type and conformance checks cannot prove those interactions. They are the final behavior contract a user experiences.

Next steps