# How Workflow Connections Work in n8n: Understanding the Directed Graph Architecture

> Explore how n8n workflow connections form a directed graph architecture. Learn how source nodes map to destinations for efficient parent and child node lookups.

- Repository: [n8n - Workflow Automation/n8n](https://github.com/n8n-io/n8n)
- Tags: internals
- Published: 2026-02-24

---

**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`](https://github.com/n8n-io/n8n/blob/main/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.

```typescript
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`, or `continueOnFail` (defined in `NodeConnectionType`)
- **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`](https://github.com/n8n-io/n8n/blob/main/packages/workflow/src/common/map-connections-by-destination.ts):

```typescript
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`](https://github.com/n8n-io/n8n/blob/main/get-parent-nodes.ts) retrieves all ancestors of a given node by querying the destination-centric map:

```typescript
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`](https://github.com/n8n-io/n8n/blob/main/get-child-nodes.ts) traverses the source-centric map to find descendants:

```typescript
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`](https://github.com/n8n-io/n8n/blob/main/get-connected-nodes.ts), which implements recursive graph traversal with support for:

- **Connection type filtering** – `main`, `error`, `ALL`, or `ALL_NON_MAIN`
- **Depth limiting** – `-1` indicates 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`](https://github.com/n8n-io/n8n/blob/main/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()`:

```typescript
// 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

```typescript
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

```typescript
// 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

```typescript
// 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`](https://github.com/n8n-io/n8n/blob/main/packages/workflow/src/interfaces.ts)** – Core type definitions (`IConnections`, `IConnection`, `NodeConnectionType`, etc.)
- **[`packages/workflow/src/common/map-connections-by-destination.ts`](https://github.com/n8n-io/n8n/blob/main/packages/workflow/src/common/map-connections-by-destination.ts)** – Inverts source-centric connections to destination-centric map
- **[`packages/workflow/src/common/get-parent-nodes.ts`](https://github.com/n8n-io/n8n/blob/main/packages/workflow/src/common/get-parent-nodes.ts)** – Convenience wrapper for "parents" lookup
- **[`packages/workflow/src/common/get-child-nodes.ts`](https://github.com/n8n-io/n8n/blob/main/packages/workflow/src/common/get-child-nodes.ts)** – Convenience wrapper for "children" lookup
- **[`packages/workflow/src/common/get-connected-nodes.ts`](https://github.com/n8n-io/n8n/blob/main/packages/workflow/src/common/get-connected-nodes.ts)** – Core recursive traversal algorithm used by both parent/child helpers
- **[`packages/workflow/src/workflow.ts`](https://github.com/n8n-io/n8n/blob/main/packages/workflow/src/workflow.ts)** – `Workflow` class that loads connections, builds both maps, and exposes `getParentNodes` / `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 `IConnections` interface, mapping source nodes to destinations via connection types like `main` and `error`.
- The **`mapConnectionsByDestination`** function 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 wrappers `getParentNodes` and `getChildNodes` supporting depth limits and connection type filtering.
- The **`Workflow`** class orchestrates these components in [`packages/workflow/src/workflow.ts`](https://github.com/n8n-io/n8n/blob/main/packages/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`](https://github.com/n8n-io/n8n/blob/main/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`](https://github.com/n8n-io/n8n/blob/main/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`](https://github.com/n8n-io/n8n/blob/main/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`](https://github.com/n8n-io/n8n/blob/main/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`](https://github.com/n8n-io/n8n/blob/main/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.