Architecture

n8n Architecture: How It Actually Works

n8n-io/n8n Sustainable Use License 1 diagram
How to read this page

Symvanta parsed this repository into a code graph: every function, class, and method is a node, and every call or import between them is an edge. Everything on this page is computed from that graph at the commit shown above. The terms:

Module (or cluster)
A group of symbols that call each other far more than they call anything else. An algorithm called Louvain community detection finds these groups from the call traffic alone; nobody draws them by hand.
Modularity (the Q number)
A 0-to-1 score of how cleanly those groups separate. Higher means more call traffic stays inside its own group; scores around 0.7 and above read as clean boundaries.
Hub
The most depended-upon symbol inside one module.
Load-bearing symbols
PageRank, the algorithm Google originally used to rank web pages, run over the call graph instead: it surfaces the functions the rest of the codebase leans on hardest.
Arrows and their numbers
How many calls cross from one module into another. A heavier arrow means tighter coupling between those two parts.
Dependency cycle
File A imports B, which imports A again, sometimes through a longer loop. Cycles are not bugs, but a change inside one tends to ripple around the whole loop.
Mutually recursive symbols
Functions that call each other, usually the natural shape of parsers and tree-walking code.

n8n is a workflow automation server. A Vue editor draws the workflow, a REST and webhook server stores it, an execution engine walks it node by node, and a large library of integration nodes makes the outbound calls. Symvanta's graph of the repo at af27b0d on master detects 778 functional modules (modularity Q=0.93), and serves the 500 largest, which hold 75,637 symbols.

The mass of the codebase is integrations. 197 of those 500 modules live in packages/nodes-base or packages/@n8n/nodes-langchain, roughly one per vendor transport, and five of the twelve repo-wide load-bearing functions are a vendor's own HTTP helper: TheHive's, Google's, OpenAI's, Microsoft Excel's and Pipedrive's. That is what an integration catalog looks like in a call graph, and this one holds 308 vendor directories and 406 credential definition files at this commit. Those 197 modules are 17.9% of the mapped symbols, behind packages/cli at 24.8% and the four AI assistant packages at 19.3%, ahead of the editor frontend at 10.8%. The execution engine is genuinely small and deliberately so: the clusters whose members sit in packages/core/src/execution-engine, packages/workflow and packages/cli/src/scaling are 6.3% of the map between them. The engine walks the graph and hands each node an execution context, and the work happens inside the node.

Module names on this page are read by hand from the packages the members live in and from each hub symbol's file. The generated cluster summaries are used for nothing here, because on this repo several of them are wrong: the cluster that holds the execution engine was summarized as logging, and the cluster that holds the REST controllers was summarized as licensing. Every cluster is cited by its id, the member-set hash that survives a reindex. Symbol counts, hubs, ids and edge weights are what the graph computed.

Module map

The diagram shows the 10 largest of the 778 detected modules, with edges weighted by how many calls cross between them. Two of the ten are the repo's own evaluation harnesses, drawn because they really are that big: the @n8n/instance-ai harness (2,650 symbols) and the AI workflow builder harness (2,082). Two more take their hub symbol from an ambient TypeScript declaration file, which inflates both their size and the weight of the arrows into them: the global interface augmentation in packages/frontend/editor-ui/src/shims-global.d.ts is referenced from 82 files, and the *.vue module shim in packages/@n8n/mcp-apps/src/apps/workflow-preview/shims-vue.d.ts from 96, in packages as unrelated as the vendored ORM and the push service. Read those two boxes as where the editor's own code sits.

n8n-io/n8n module map: the 10 largest of 778 detected modules with call-weighted edges, generated by Symvanta
Module map of n8n-io/n8n, generated by Symvanta. Link to this diagram Open full size

Where to start reading

These are the load-bearing functions: the PageRank ranking over the call graph, most depended-upon first. Four of the names are defined independently in several packages (apiRequest alone in six vendor transports), so each link points at the definition that hubs the largest cluster carrying that name. Five of the twelve are one vendor's HTTP transport, which is the shape of this repo showing up in the ranking.

Key subsystems

Instance AI assistant runtime

Module id 1122f926faac11ce, 3,706 symbols, the largest on the map. Its members live in packages/@n8n/instance-ai/src and its hub is InstanceAiContext from src/types.ts, the context object every assistant tool receives. InstanceAiToolRegistry, OrchestrationContext, WorkflowBuildOutcome and ModelConfig sit beside it. Its heaviest outgoing edge is 148 calls into the editor's agents feature, which is the other half of the same product surface.

Node errors and credential helpers

Module id 532185b8a99ea6b4, 3,337 symbols. The hub is NodeApiError from packages/workflow/src/errors/node-api.error.ts, and the members are NodeOperationError plus a wide slice of the credential and transport helpers under packages/nodes-base (beeminderApiRequest, getActiveCredentialType, getHost, DatabricksCredentials). Integration nodes raise these two error types when a request fails, so the error classes cluster with the transports that throw them instead of with the rest of the workflow package.

Agents SDK types and JSON values

Module id 4e0cee763e689c87, 3,183 symbols, hubbed on JSONValue from packages/@n8n/agents/src/types/utils/json.ts. This is the type surface of the agents SDK: JSONObject, JSONArray, AgentMessage, AgentDbMessage, BuiltTool. A one-line recursive type alias ranks as the hub here because every message and tool definition in the SDK is built out of it.

Editor UI composables and log view

Module id f7c4c628d73c66c6, 2,887 symbols, members under packages/frontend/editor-ui/src/app/composables: useWorkflowDocumentStore, useTelemetry, and the LogEntry and NodeLogEntry shapes the execution log view renders. This is one of the two clusters whose hub is an ambient declaration, so the name comes from where the members live.

REST controllers and response errors

Module id ad43c0ce9514e986, 2,871 symbols, hubbed on ResponseError from packages/cli/src/errors/response-errors/abstract/response.error.ts with BadRequestError and ForbiddenError under it. Around them sit the chat hub module (ChatHubSession, ChatHubTool) and the license accessors (License.manager, License.isLicensed). Controllers throw response errors and check the license on the same request path, so the graph puts them together. Its heaviest edges are 116 calls into the server agents module and 102 into the database entities.

Server agents and the editor agents feature

The server side is module id 1fbd1c236a830671, 2,185 symbols under packages/cli/src/modules/agents, hubbed on the Agent entity in entities/agent.entity.ts, with AgentHistory, AgentExecution, AgentExecutionThread and AgentRepository. The editor side is module id 43d35099e877cd82, 2,541 symbols under packages/frontend/editor-ui/src/features/agents/composables. The server module takes 116 calls from the REST controller cluster and sends 46 back.

Execution engine and queue runner

The engine is two modules. Id 83094e44dd365222 (1,255 symbols) has its members in packages/core/src/execution-engine while its hub is Logger.scoped from packages/@n8n/backend-common, which is why its generated summary described logging. Id 8d9cdb44e96f26ba (540 symbols) is the partial-execution machinery: DirectedGraph, GraphConnection, NodeExecutionContext, Workflow.getNode. The queue runner is another two: id c4a90fa06ac89e11 (616 symbols) under packages/cli/src/scaling, holding ExecutionRef, ActiveExecutions and the execution lifecycle hook handlers, and id e4d215ec66e79a25 (399 symbols) for the pub/sub commands workers exchange. Together with packages/workflow, that is 6.3% of the mapped symbols.

Integration transports

The 197 integration modules are the long tail of the map, each one a vendor's transport file plus the actions that call through it, 13,575 symbols in total. Past the error cluster above, the largest are id a1594a6836dd063d (787 symbols) hubbed on googleApiRequest from the Gmail helpers, id be2b32f6e5a912ef (660) on pipedriveApiRequest, id f6c860f8c23c0180 (630) on getGoogleAccessToken, and id 4419e5cfc5c6b81d (440) on the shared verifySignature webhook helper. The pattern repeats verbatim down the tail: one *ApiRequest function per vendor, a credentials type, and a directory of actions that call it. Symvanta's clustering gives each of those its own module, which is why 197 of the 500 served modules are integrations while they are only 17.9% of the symbols.

Workflow error hierarchy

Module id 91ba1b8bb399e612, 1,891 symbols in packages/workflow/src: BaseError, NodeError, ExecutionBaseError, the JsonObject and JsonValue types, and helpers like removeCircularRefs. This is the package every other package imports its error classes from.

Evaluation harnesses

Two of the ten drawn clusters are test harnesses for the AI features: id ca1dbdc7a8b683df (2,650 symbols, packages/@n8n/instance-ai/evaluations, hubbed on the harness N8nClient) and id 20557a094fd53329 (2,082 symbols, packages/@n8n/ai-workflow-builder.ee/evaluations). With the rest of packages/testing they are 9.8% of the mapped symbols. They rank this high because these harnesses drive a real n8n instance, so they carry client, workflow and validation code of their own.

Canonical request flow

The sequence worth reading first is one workflow execution: the path from the call that starts a run to the node's own execute method and back. Every step below is a call edge out of the step above it, in the order the work happens.

  1. WorkflowRunner.run (packages/cli/src/workflow-runner.ts:242) is the entry point 13 files in the server call: retries, waiting executions resumed by the wait tracker, chat runs, the MCP execute and test tools, and the evaluation runner.
  2. CredentialsPermissionChecker.check (packages/cli/src/executions/pre-execution-checks/credentials-permission-checker.ts:102) maps every credential id the workflow's nodes use to the projects the workflow belongs to, and refuses the run when one of them is not shared with any of those projects.
  3. ActiveExecutions.add (packages/cli/src/active-executions.ts:62) reserves a concurrency slot, creates the execution row at status new, and builds the in-memory record everything later attaches to: the cancellation controller, the response promise, the post-execute promise.
  4. WorkflowRunner.enqueueExecution (packages/cli/src/workflow-runner.ts:515) is the queue-mode branch: it hands the job to ScalingService.addJob and waits for a worker to report the result. The worker picks it up in JobProcessor.processJob and runs step 9 onwards in its own process.
  5. WorkflowRunner.runMainProcess (packages/cli/src/workflow-runner.ts:343) is the other branch, running the workflow in the server process. It registers the lifecycle hooks, arms the execution timeout, and owns the failure paths.
  6. ExecutionRepository.setRunning (packages/@n8n/db/src/repositories/execution.repository.ts:380) flips the row to running inside a transaction and keeps the original startedAt when this is a resumed execution.
  7. ManualExecutionService.runManually (packages/cli/src/manual-execution.service.ts:49) decides what a manual run actually executes: the whole workflow from its trigger, or a partial re-run seeded with the pinned data and start nodes the editor sent.
  8. DirectedGraph.fromWorkflow (packages/core/src/execution-engine/partial-execution-utils/directed-graph.ts:466) turns the stored workflow into the graph object the partial-execution logic walks to find which nodes a re-run has to touch.
  9. WorkflowExecute.processRunExecutionData (packages/core/src/execution-engine/workflow-execute.ts:1586) is the loop, roughly 970 lines of it. It shifts the next node off the execution stack, checks that its input data is ready, runs it, pushes the nodes its output feeds, and repeats until the stack is empty. Per-node retries and the wait state live inside the same loop.
  10. WorkflowExecute.runNode (packages/core/src/execution-engine/workflow-execute.ts:1311) handles one node: disabled nodes, execute-once, trigger and poll nodes, and the error-output branch that lets a workflow continue past a failure.
  11. WorkflowExecute.executeNode (packages/core/src/execution-engine/workflow-execute.ts:1050) builds an ExecuteContext and calls INodeType.execute. This one edge is the boundary between the engine and the integration library: above it is n8n's code, below it is the node's, and the 197 transport modules hang off the far side.
  12. ExecutionLifecycleHooks.runHook (packages/core/src/execution-engine/execution-lifecycle-hooks.ts:118) runs the handlers registered for one of eight events (workflowExecuteBefore, nodeExecuteBefore, nodeExecuteAfter, nodeFetchedData, sendResponse, sendChunk, workflowExecuteResume, workflowExecuteAfter). Saving progress, pushing status to the editor and recording statistics all hang here.
  13. ActiveExecutions.finalizeExecution (packages/cli/src/active-executions.ts:246) closes a streaming response if the run had one, resolves the post-execute promise, and drops the in-memory record.

Health signals

Symvanta detected 255 dependency cycles across 778 modules (modularity Q=0.93). The largest cycle spans 269 files in the services area. 85 sets of mutually recursive symbols were also detected, the largest being workflow (53 symbols). Read the cycle count with its composition in hand: 194 of the 255 sit entirely inside packages/@n8n/typeorm, the TypeORM fork the repo vendors, which the clustering already excludes (it holds zero of the 778 modules) while the cycle listing still covers it. The 61 that remain are n8n's own. The largest is the one worth acting on: every file the map names for it sits under packages/cli/src, including services/role.service.ts, active-executions.ts, the MCP tool files and the agents services, all reaching each other through imports. The second largest spans 91 files in packages/workflow. The recursion is milder and mostly deliberate: the 53-symbol workflow group is the node interface surface in packages/workflow/src/interfaces.ts (IExecuteFunctions, ILoadOptionsFunctions, INodeParameters and their neighbours), which is what a self-referential parameter type looks like in a graph.

See your own codebase mapped like this. Free for 7 days, no credit card.

Start free trial →

Auto-generated by Symvanta from the public repo n8n-io/n8n at commit af27b0d , licensed Sustainable Use License .

Machine-readable companion: data.json (module counts, subsystems, load-bearing symbols, health signals).

Get this for your codebase →