Skip to main content
The execution API separates permanent graph edits, prompt-time resolution, queue submission, and backend results. Keeping those stages distinct prevents a pack from mutating the live document merely to construct a prompt.

Queueing a run

run() behaves like the host Run action. It resolves when submission finishes, not when backend execution completes. false means another queue call was already in flight and this call was folded into it. Run part of the graph with explicit output nodes:
The host includes the dependencies that feed those nodes. An empty nodes array is rejected rather than interpreted as “run everything.”

Queue lifecycle

Before submission

The listener runs before the prompt is built. It must be synchronous; the prompt builder does not await work started here. A returned cleanup runs when the attempt ends whether it was submitted, rejected, canceled, or threw. This pairing exists for legacy behavior that must temporarily change graph state and restore it, but prefer beforeSerialize, a frontend resolver, or a supplier when those express the intent without graph mutation.

After submission

onAfterRun means the submission attempt finished. It is not an execution- complete event. Accepted submissions include prompt IDs and backend node counts; rejected reports how many submissions the backend refused.

Validation rejection

This event covers a prompt the backend rejects before execution begins. It includes the top-level error and per-node input validation details. It does not represent a transport failure or an exception raised after execution starts.

Guarding a run

Use a guard when the decision may be asynchronous and must be made before the prompt is built:
Every registered guard runs; any false cancels the attempt. A guard that throws is treated as allowing the run. All guards share a short host timeout, after which the run proceeds so one extension cannot make ComfyUI permanently unrunnable. Do not place an indefinitely blocking dialog behind a guard. onBeforeRun observes and prepares. guard can delay and cancel. Do not use one as an approximation of the other.

Queue state and interruption

pending() includes the currently executing run. interrupt() stops that run; it does not clear the remainder of the queue. The API also exposes the host’s user-facing queue settings:
Use disableAutoQueue() before a self-interrupting conditional workflow so the automatic runner does not immediately submit it again.

Observing backend execution

The root API resolves backend execution IDs, including nested subgraph paths:
executingNode() is undefined between nodes and runs. Use executionNode(id) instead of parsing nested execution IDs or looking up the visible graph by the final numeric segment. For results, register behavior on the node definition:
ExecutionResult.raw preserves custom output keys from the pack’s own backend. PreviewFrame.url is an object URL revoked when the next frame arrives; copy or consume it before retaining a preview beyond that lifetime.

Frontend-only nodes

Frontend nodes remain ordinary editor entities but do not execute on the backend. Define one with execution: 'frontend', or mark a backend-defined type with NodeDefBuilder.setExecution('frontend', resolver?). A resolver answers what each of its own outputs means:
Each output name maps to one OutputResolution:
The resolver receives a frozen ResolveView:
  • self gives the resolver’s ID, type, properties, groups, mode, color, own inputs and outputs, and widget values;
  • nodesOfType(type) returns other frozen views in the same graph scope;
  • self.input(nameOrIndex) creates the only reference a resolver may forward.
Resolution follows chains to a physical backend output, literal, or omission, with cycle detection. A resolver must be pure: it cannot edit the graph or a prompt draft. It may return a promise, and prompt construction awaits it. The synchronous readers cannot wait, so input.resolvedSource() and resolvedSupplies() report a pending resolver as unresolved. InputSlotHandle.resolvedSource() exposes the same final result for editor behavior without changing topology.

Suppliers and broadcast behavior

A supplier is the supply-side counterpart to a resolver. It answers which unconnected inputs elsewhere in the same graph this node offers to feed:
A supplied source can be:
  • one of the supplier’s own outputs;
  • a literal;
  • whatever feeds one of the supplier’s own inputs (forwardInput).
It cannot name an arbitrary third-party node as a source. A supplier may offer what it owns, not rewire two bystanders. unconnectedInputs() exposes matching data needed by real broadcast packs: slot name, translated label, type, widget-input status, owner title/mode/color, groups, and frozen owner properties. When multiple suppliers claim one input, higher priority wins. Exact priority ties feed nothing instead of making execution depend on graph order. Resolution runs independently in each graph scope. It never crosses a subgraph boundary.

Inspecting winning supplies

This recomputes the same pure resolver and priority arbitration prompt execution uses. It is the safe basis for a command such as “convert virtual broadcasts to real links”; reimplementing matching in the pack can create links the prompt would not use. The returned IDs are local to that graph scope. GraphScopeHandle offers the same read for root or subgraph definitions.

Prompt-time widget serialization

Frontend resolution changes topology. A widget’s beforeSerialize changes one value for one destination:
Use this for sentinel expansion, prompt templates, rolled seeds, or embedded reproduction data. It is synchronous and does not mutate the live widget. Do not edit the built prompt or workflow snapshot. Use partial queue execution, frontend resolution, supply, widget serialization, and ordinary graph commands for the supported intents those internal edits previously combined.