Skip to main content
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.

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:

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:
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:
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. 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.