> ## Documentation Index
> Fetch the complete documentation index at: https://docs.comfy.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Connection Callback Migration for Frontend Extensions

> Ordering of onConnectionsChange and node removal callbacks, SubgraphIO at subgraph boundaries, and graph.events in ComfyUI extensions.

Callback signatures did not change. `onConnectionsChange`, `onConnectInput`,
`onConnectOutput`, and `onBeforeConnectInput` keep their existing arguments
and timing. For link registration and `LinkNetwork` API changes, see
[Link registration migration](https://docs.comfy.org/custom-nodes/js/link-registration-migration.md).

## Check for SubgraphIO at subgraph boundaries

At a subgraph boundary (a `SubgraphInput` or `SubgraphOutput`),
`onConnectionsChange` still fires, but its fifth argument is a `SubgraphIO`,
not the usual `INodeInputSlot`/`INodeOutputSlot`. Check for it before
treating the argument as a normal slot. The frontend's own `isSubgraphInput`
and `isSubgraphOutput` guards are not exposed to extensions, so check for the
`linkIds` array and `parent` node that every `SubgraphIO` has and node slots
do not:

```js theme={null}
function isSubgraphIO(slot) {
  return slot != null && Array.isArray(slot.linkIds) && 'parent' in slot
}

nodeType.prototype.onConnectionsChange = function (
  type,
  index,
  isConnected,
  link,
  slotOrIO
) {
  if (isSubgraphIO(slotOrIO)) return
  // slotOrIO is INodeInputSlot | INodeOutputSlot
}
```

## Subscribe to node lifecycle events with graph.events

`graph.events` dispatches `node:added`, `node:before-removed`, and
`node:removed`. Use these when more than one part of your extension needs to
observe a node being added or removed:

```ts theme={null}
graph.events.addEventListener('node:added', (e) => {
  console.log('added', e.detail.node.id)
})
```

`node.onAdded`, `node.onRemoved`, and the single-slot
`graph.onNodeAdded`/`graph.onNodeRemoved` still work, but assigning
`graph.onNodeAdded` replaces any existing handler: a second subscriber
silently drops the first. Prefer `graph.events` unless you need to veto or
mutate during add/remove. There's no multi-subscriber equivalent for
connect/disconnect; `onConnectionsChange` is still the only per-connection
hook.

## Node removal fires callbacks in this order (partially tested)

Removing a node fires, in order:

1. `node:before-removed`: the node is still fully attached.
2. `onConnectionsChange(..., false, ...)` on the removed node and on each
   connected peer, while the removed node is still in the graph.
3. `node.onRemoved()`: `node.graph` is still set.
4. The node detaches from graph indexes and stores; `node.graph` becomes
   `null`.
5. `onNodeRemoved(node)`, then `node:removed`, both seeing an already-detached
   node.

Tests cover `node:before-removed → node.onRemoved() → onNodeRemoved(node)` and
separately verify that `node:removed` sees a detached node. The complete
sequence, including step 2, is observed from source rather than guaranteed by
one end-to-end test.

To resolve the removed node from a peer's `onConnectionsChange` handler, do it
at step 2 via `graph.getNodeById()`. By `node:removed`, the node is gone.

## Workflow load resolves peer nodes before onConnectionsChange runs

`graph.configure()` creates every node in the workflow before any node's own
`configure()` runs and fires `onConnectionsChange` for its restored links.
So when your `onConnectionsChange` callback fires during load, every peer
node already exists and its link is already registered, so `graph.getNodeById()`
and link lookups from inside that callback resolve correctly.

## API prompt import does not reconnect resolved inputs

`app.loadApiJson()` connects each resolvable input without running a second
disconnect/reconnect pass. Extensions should not rely on duplicate
`onConnectionsChange` notifications during API prompt import. Make each
connection handler perform its work on the first notification; move work that
requires the complete imported graph to `afterConfigureGraph` or `afterLoadGraph`.

Inputs whose source output or destination slot does not exist yet are retried
while a pass makes progress. This preserves dynamic-input behavior: a successful
connection can create the slot needed by a later pending connection. Already
resolved connections are not repeated, and a connection rejected by an
extension is not retried once its endpoints are available.

This changes API prompt import only. Regular workflow loading still uses
`graph.configure()` as described above, and connection callback signatures are
unchanged.

## Reject connections before they are made

To refuse a connection, return `false` from `onConnectOutput` on the source
node or `onConnectInput` on the target node. Both run before the graph changes
anything, so an existing link on the target input stays in place:

```js theme={null}
nodeType.prototype.onConnectOutput = function (
  slot,
  type,
  input,
  targetNode,
  targetSlot
) {
  return !shouldReject(type, targetNode)
}
```

Don't reject from `onConnectionsChange`. By the time it runs, the new link has
already replaced any link the target input had, so removing the new link
leaves the input disconnected.

## Removing a link inside the output callback skips the input callback

Connecting a link calls the output side's `onConnectionsChange` first, then
the input side's. If the output-side handler removes that same link before
returning, the input side gets an `isConnected === false` callback from the
removal, and its `isConnected === true` callback for the new link is skipped.
Don't assume both sides always see the connection; check that the link you
were handed is still connected if your handler re-enters the graph. This is
observed behavior, not a tested contract: no test currently exercises the skip
path.

## Replace removed slot events

`node:slot-links:changed` and `node:slot-errors:changed` were removed, with
no replacement event of the same name. Migrate to:

* `node.isInputConnected()`, `node.isOutputConnected()`, or
  `node.getOutputNodes()` for a reactive read of connectivity state, or
* `onConnectionsChange` when you need to run code at the exact moment a slot
  connects or disconnects.

## Read connection state from callback arguments or node methods

`input.link` and `output.links` are deprecated, and reads can drift from the
current graph. Writes only remove links: assigning `null` to `input.link`
disconnects the input, and assigning or mutating `output.links` disconnects
every link missing from the new array. Writes never create links; use
`node.connect()`, `node.disconnectInput()` or `node.disconnectOutput()`. Use the
`link_info` argument your callback already receives, or the `LGraphNode`
instance methods that read the link store directly:
`node.isInputConnected()`, `node.getInputLink()`, `node.isOutputConnected()`
and `node.getOutputNodes()`. Don't read `input.link` or `output.links`.

The store functions behind these methods are internal and not part of the
extension API. Use the node methods.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.