Skip to main content
comfy.graph is the editing surface for the graph currently shown to the user. It provides graph-safe node, selection, viewport, group, link, and mutation operations without exposing LGraph or LGraphCanvas.

Visible graph, root graph, and subgraphs

The active view is not always the document root:
A subgraph entry represents a definition, not each placed instance. If the same definition is placed three times, it appears once and its internal nodes appear once. Node IDs must be resolved inside their owning graph. Do not collect all document nodes into a map keyed only by ID:
Use node.graphId when persisting pack-owned in-memory state about a node.
Every list is a frozen snapshot. LinkInfo is inert data with source and target node IDs, slot IDs, types, and endpoint indexes at snapshot time. Indexes are volatile; do not store them across slot mutations. Use node.inputs and node.outputs to edit connectivity. graph.links() is for inspection, not mutation. graph.nodeAt({ x, y }) returns the topmost node at a graph-space point using the rendered layout and z-order. It can find nothing before the first render.

Creating and removing nodes

add() constructs through the registered definition. It throws when the type does not exist. duplicate() carries serializable widgets and properties but does not copy links. It returns undefined when the source is gone or cannot be constructed. NodeHandle.remove() removes its own node. graph.remove(id) is the equivalent when only an ID is available.

Replacing a node

Node type is identity and is read-only. Rebuild with replace():
The operation carries position, a user-customized title, colors, mode, compatible properties, widget values by name, and every link that still fits. It matches slots by name first and by index as a fallback. Incompatible links are dropped with a warning rather than forced into the wrong slot. The complete replacement is one undo step. Replacing with the same type is useful when a refreshed definition changed and an existing node must be rebuilt without discarding its state.

Compound edits and undo

Use batch() for one synchronous user operation:
The scope closes even when the callback throws. It is synchronous by design; never include an await inside it.

Selection and viewport

Selection and viewport methods address the visible graph and active editor. pointerPosition() is in graph coordinates and can return undefined when no canvas is available. centerOn() does not change zoom. For a panel anchored to a node, read node.getScreenRect() and update it when the viewport changes:
Do not read or reconstruct canvas pan, scale, offsets, title height, or device pixel ratio.

Groups

GroupHandle provides:
  • getTitle() / setTitle();
  • getColor() / setColor();
  • nodes() for the nodes geometrically contained by the group;
  • getBounds() in graph space;
  • centerOn() for the visible view.
Groups are derived rectangles, not parents that own a stored child list. Ask nodes() again after layout changes. A subgraph scope’s groups() reads groups inside that definition.

Structural version token

graph.version changes when graph-visible state changes, including node or slot structure, connections, node flags, and widget values committed through the host protocol. Treat it as an opaque token:
Do not subtract versions, assume increments of one, or use it as a replayable event log. Pack state held outside the graph and widget model does not affect it. A custom canvas widget whose external drawing data changed should call its own redraw(). graph.cacheSize is diagnostics for live handle-cache slots, not application state.

Node and graph observation

There is no per-frame tick. Observe the semantic operation:

Node property changes

The default scope is visible. scope: 'document' includes the root and every subgraph definition. Each event names its graph because node ID alone is not a document-wide key. The tracked fields are title, mode, foreground color, background color, shape, and advanced-widget visibility. Position uses the movement stream instead because it changes continuously during a drag.

Movement and drag completion

A pack that moves nodes from its own movement handler must guard against re-entry. onNodeDragEnd is available under Nodes 2.0; the legacy renderer does not publish a drag lifecycle.

Editor interaction state

comfy.isInteracting() reports whether the editor is already handling a link, node, or widget gesture. A pack starting its own pointer gesture should stand down while it is true.

Resolved supply inspection

graph.resolvedSupplies() runs the same pure supplier and priority arbitration used by prompt construction and returns the winning graph-local edges without mutating the graph. It is intended for editor commands that materialize virtual broadcasts as real links. Exact-priority ties are absent, matching execution. See Execution and resolution for supplier semantics.