How Workflow Connections Work in n8n: Understanding the Directed Graph Architecture
n8n stores workflow connections as a directed graph using an IConnections object that maps source nodes to destinations, then inverts this map via mapConnectionsByDestination to enable efficient parent and child lookups through getParentNodes and getChildNodes.
Workflow connections in n8n form the backbone of automation logic, defining how data flows between nodes. In the n8n-io/n8n repository, these connections are implemented as a sophisticated directed graph structure that supports both forward and backward traversal. Understanding this architecture is essential for developers building custom nodes or extending the workflow engine.
The IConnections Data Structure
At the core of n8n's connection system is the IConnections interface defined in packages/workflow/src/interfaces.ts. This structure represents a source-centric directed graph where each key is a source node name pointing to its outgoing connections.
export interface IConnections {
// source node name → { [connection type]: NodeInputConnections }
[sourceNode: string]: INodeConnections;
}
The hierarchy flows through three levels:
- Source node – the node where data originates
- Connection type – such as
main,error, orcontinueOnFail(defined inNodeConnectionType) - Input index – numeric index for multi-input nodes
- IConnection array – destination details containing
{ node: destinationName, type: 'main', index: 0 }
Inverting the Graph for Efficient Lookups
Because most operations require finding incoming connections (e.g., "which nodes feed into this node?"), n8n inverts the source-centric map into a destination-centric map. This transformation is handled by mapConnectionsByDestination in packages/workflow/src/common/map-connections-by-destination.ts:
export function mapConnectionsByDestination(connections: IConnections) {
const returnConnection: IConnections = {};
// Iterates over source → type → inputIndex → each connectionInfo
// Builds returnConnection[destNode][destType][destIndex] = [{node: source, …}]
return returnConnection;
}
This inversion creates a mirror structure where keys are destination node names, enabling O(1) lookups of parent nodes rather than O(n) scans through all connections.
Traversing Workflow Connections
With both maps available, n8n answers two fundamental graph questions through specialized utilities in packages/workflow/src/common/:
Finding Parent Nodes
The getParentNodes function in get-parent-nodes.ts retrieves all ancestors of a given node by querying the destination-centric map:
export function getParentNodes(connectionsByDestinationNode, nodeName, type = NodeConnectionTypes.Main, depth = -1) {
return getConnectedNodes(connectionsByDestinationNode, nodeName, type, depth);
}
Finding Child Nodes
Conversely, getChildNodes in get-child-nodes.ts traverses the source-centric map to find descendants:
export function getChildNodes(connectionsBySourceNode, nodeName, type = NodeConnectionTypes.Main, depth = -1) {
return getConnectedNodes(connectionsBySourceNode, nodeName, type, depth);
}
Core Traversal Algorithm
Both helpers rely on getConnectedNodes from get-connected-nodes.ts, which implements recursive graph traversal with support for:
- Connection type filtering –
main,error,ALL, orALL_NON_MAIN - Depth limiting –
-1indicates unlimited traversal, while positive integers limit the search to specific hop counts
The Workflow Class Implementation
The Workflow class in packages/workflow/src/workflow.ts orchestrates these components. It loads the raw connections object from workflow definitions, stores the source-centric map as connectionsBySourceNode, and generates the inverted map via setConnections():
// packages/workflow/src/workflow.ts – setConnections()
this.connectionsBySourceNode = connections;
this.connectionsByDestinationNode = mapConnectionsByDestination(this.connectionsBySourceNode);
Higher-level code throughout the execution engine, UI canvas, and webhook helpers interacts with the Workflow instance, calling workflow.getChildNodes() or workflow.getParentNodes() to navigate the graph without managing the underlying connection maps directly.
Practical Code Examples
Creating a Workflow and Inspecting Connections
import { Workflow } from '@n8n/workflow';
import type { INode, IConnections } from '@n8n/workflow';
// Two nodes: "Start" → "HTTP Request"
const nodes: INode[] = [
{ name: 'Start', type: 'n8n-nodes-base.start', typeVersion: 1, position: [0, 0], parameters: {} },
{ name: 'HTTPRequest', type: 'n8n-nodes-base.httpRequest', typeVersion: 1, position: [200, 0], parameters: {} },
];
// Source-centric connections object (as stored in the DB)
const connections: IConnections = {
Start: {
main: {
0: [{ node: 'HTTPRequest', type: 'main', index: 0 }],
},
},
};
const workflow = new Workflow({
id: 'wf1',
name: 'Demo',
nodes,
connections,
active: true,
nodeTypes: /* nodeTypes registry – omitted for brevity */,
});
console.log('Children of Start →', workflow.getChildNodes('Start')); // ["HTTPRequest"]
console.log('Parents of HTTPRequest →', workflow.getParentNodes('HTTPRequest')); // ["Start"]
Traversing a Deeper Graph with Depth Limits
// Add a third node "Set"
nodes.push({
name: 'Set',
type: 'n8n-nodes-base.set',
typeVersion: 1,
position: [400, 0],
parameters: {},
});
connections.Start.main[0].push({ node: 'Set', type: 'main', index: 0 });
connections.HTTPRequest = {
main: {
0: [{ node: 'Set', type: 'main', index: 0 }],
},
};
workflow.setConnections(connections);
// All descendants of "Start" (unlimited depth)
console.log(workflow.getChildNodes('Start')); // ["HTTPRequest", "Set"]
// Only immediate children (depth = 1)
console.log(workflow.getChildNodes('Start', undefined, 1)); // ["HTTPRequest"]
Accessing the Destination-Centric Map Directly
// Direct access – useful for UI where incoming connections are needed
const inbound = workflow['connectionsByDestinationNode']; // internal but accessible
console.log(inbound['Set']); // { main: [ [ { node: 'Start', type: 'main', index: 0 },
// { node: 'HTTPRequest', type: 'main', index: 0 } ] }
Key Source Files
packages/workflow/src/interfaces.ts– Core type definitions (IConnections,IConnection,NodeConnectionType, etc.)packages/workflow/src/common/map-connections-by-destination.ts– Inverts source-centric connections to destination-centric mappackages/workflow/src/common/get-parent-nodes.ts– Convenience wrapper for "parents" lookuppackages/workflow/src/common/get-child-nodes.ts– Convenience wrapper for "children" lookuppackages/workflow/src/common/get-connected-nodes.ts– Core recursive traversal algorithm used by both parent/child helperspackages/workflow/src/workflow.ts–Workflowclass that loads connections, builds both maps, and exposesgetParentNodes/getChildNodes
These files collectively define how n8n represents, stores, and traverses workflow connections, enabling the execution engine, UI canvas, and webhook helpers to reason about data flow between nodes.
Summary
- n8n workflow connections are stored as a directed graph using the
IConnectionsinterface, mapping source nodes to destinations via connection types likemainanderror. - The
mapConnectionsByDestinationfunction inverts the source-centric map to create a destination-centric view, enabling O(1) parent node lookups. - Graph traversal is handled by
getConnectedNodes, with convenience wrappersgetParentNodesandgetChildNodessupporting depth limits and connection type filtering. - The
Workflowclass orchestrates these components inpackages/workflow/src/workflow.ts, exposing simple methods for navigating the connection graph while managing the underlying complexity.
Frequently Asked Questions
What data structure does n8n use to store workflow connections?
n8n uses a directed graph represented by the IConnections interface defined in packages/workflow/src/interfaces.ts. This structure maps each source node name to an object containing connection types (like main or error), which then point to arrays of IConnection objects describing the destination nodes. The system maintains both this original source-centric map and an inverted destination-centric map for efficient bidirectional traversal.
How does n8n find parent nodes during workflow execution?
The execution engine calls workflow.getParentNodes(nodeName) from the Workflow class in packages/workflow/src/workflow.ts. This method queries the destination-centric connection map generated by mapConnectionsByDestination, which inverts the original source-centric structure to enable O(1) lookups of incoming connections. The underlying getConnectedNodes function in packages/workflow/src/common/get-connected-nodes.ts then performs a recursive traversal respecting connection types and optional depth limits.
What is the difference between source-centric and destination-centric connection maps?
The source-centric map (connectionsBySourceNode) stores connections indexed by the node where data originates, making it efficient to answer "where does this node send data?" The destination-centric map (connectionsByDestinationNode) is created by mapConnectionsByDestination in packages/workflow/src/common/map-connections-by-destination.ts and indexes connections by the receiving node, optimizing "which nodes feed into this node?" queries. The Workflow class maintains both maps to support bidirectional graph traversal without performance penalties.
How can I traverse workflow connections programmatically in custom nodes?
Import the Workflow class from @n8n/workflow and instantiate it with your nodes and connections, then call workflow.getChildNodes(nodeName, connectionType, depth) or workflow.getParentNodes(nodeName, connectionType, depth). For advanced use cases, access the underlying getConnectedNodes function from packages/workflow/src/common/get-connected-nodes.ts directly, passing either the source-centric or destination-centric connection map depending on traversal direction. Specify connection types like NodeConnectionTypes.Main or NodeConnectionTypes.Error to filter which edges to follow during traversal.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →