Skip to main content
Node type registration and live node editing are separate APIs. Use the definition registry to install behavior by type, and a NodeHandle to work with one node that already belongs to a graph.

Reading definitions

NodeDef is frozen metadata: inputs[].options and hidden deliberately preserve pack-owned backend data. They are declarations, not live widget values or hidden-input execution values. Use defs.all() to take a snapshot of every definition and defs.has(type) for an existence check. defs.refresh() asks the backend to reload definitions; defs.onRefreshed() observes completion.

Extending definitions

Builder registrations compose. The host applies every matching extension rather than making each pack capture and call a previous prototype method.

Builder configuration

Structural changes to a live node (dynamic slots, values, ordering, or connections) belong on the instance handles supplied by lifecycle callbacks.

Lifecycle and behavior hooks

onBeforeConnect returns false to veto. onUnplacedLink returns true after the callback wires the link itself. onDragOver returns true to route the drop; onDrop returns true to claim it. onSerialize must return deterministic, synchronous, pack-owned data. Its data comes back through onConfigured. Do not return core workflow fields or mutate a serialization object supplied by the host.

Defining a frontend-owned node

NodeDefinition is plain data, not a class. It supports inputs, outputs, widgets, frontend execution, supply, and the common lifecycle callbacks. For builder-only behavior such as menu entries, preview frames, resize, hover, double-click, connect veto, or unplaced links, define the type and extend the same type separately.

Working with a live node

Node handles can come from lifecycle callbacks, graph lookups, selection, groups, execution events, or subgraph scopes.

Identity and presentation

  • id is identity inside the owning graph.
  • graphId identifies that graph.
  • type is immutable definition identity.
  • comfyClass is the backend class identifier when it differs from type.
  • title, mode, collapsed state, pinned state, colors, and shape use explicit getter/setter pairs.
Do not assign type. Use comfy.graph.replace(node.id, newType) to rebuild the node and preserve compatible state and links.

Properties

Use getProperty, getProperties, and setProperty. getProperties() returns an inert object rather than a mutable reference. onPropertyChanged can normalize or reject a user edit:
setValue() replaces the pending property value without re-entering the property callback. reject() restores the previous value.

Geometry

Do not reconstruct title height, slot spacing, pan, zoom, or device-pixel-ratio math from renderer constants.

Output images

getOutputImages() returns URLs for the images or previews the node currently exposes. It never returns renderer-owned HTMLImageElement objects. getDisplayedImageIndex() identifies the image selected or hovered by the user, or returns undefined when there is no such choice.

Collections and snapshots

Each node exposes:
These are operation-oriented collections, not arrays. See Slots and links and Widgets. node.snapshot() returns a frozen NodeSnapshot with identity, presentation, position, and size. It can return undefined when the entity is gone. Node menu entries are declarative and host-rendered:
An item may instead provide one level of items, either a fixed array or a function of the current node. A submenu parent omits run. Badges are small labels rendered in node chrome under both renderers:
Pass a function for a dynamic badge. It runs during drawing, so it must be fast. The return value removes the badge.

Pack-owned instance state

Do not add fields to a node or handle. Keep state outside the entity and include graph scope in the key:
When handles may come from different API instances or scopes, compare them with comfy.sameEntity() rather than ===.