# n8n-io/n8n — codebase unpack

> Canonical page: https://codevetter.com/unpack/n8n

This is a deterministic Repo Unpack scan of n8n-io/n8n — a structural map, not a review, security audit, or maintainer-endorsed description.

- Scanned commit: 1a370b965c1fe438dfa58f371a01080eb9cca776 (2026-09-22)
- Last upstream commit: feat(editor): Support external dropdown menu input (no-changelog) (#39188)
- Generated by codevetter 1.12.0 on 2026-09-22
- 3998 files scanned · 17.0 MB

## Languages

- TypeScript: 21195 files
- JSON: 3831 files
- Vue: 1308 files
- Markdown: 687 files
- YAML: 384 files
- JavaScript: 359 files
- Sass: 100 files
- Python: 69 files

## Repository structure

- packages/ — 3198 files
- docs/ — 284 files
- .github/ — 229 files
- scripts/ — 89 files
- .agents/ — 64 files
- .claude/ — 34 files
- patches/ — 21 files
- .devcontainer/ — 20 files

Probable source entrypoint: `packages/` (inferred from the tree, not upstream docs)

## Package

- package.json: n8n-monorepo v2.41.0 — 23 declared dependencies

## Structural health signals

Average score 7/10 across 96 files; 16 hotspots. These are deterministic review leads, not proof of a bug.

## Recent history

- 1a370b965c1f (2026-09-22) feat(editor): Support external dropdown menu input (no-changelog) (#39188)
- 5b149495c56a (2026-09-22) feat(ai-builder): Grade token consumption and memory compaction in Instance AI evals (no-changelog) (#38974)
- 340d36c860df (2026-09-22) refactor(editor): Relocate MCP-shared constants and test helper (no-changelog) (#39196)
- 870880963ddd (2026-09-22) feat(Databricks Node): Add the Pipeline resource to the hidden Databricks Trigger (no-changelog) (#38827)
- 1308aae1b79f (2026-09-22) feat(Databricks Node): Poll pipeline update events for the Databricks Trigger (no-changelog) (#38842)

## Overview

n8n is a self-hostable workflow-automation platform: a TypeScript pnpm/Turbo monorepo whose `packages/cli` (`n8n` binary) runs an Express server plus CLI commands, `packages/core` executes workflow DAGs through `WorkflowExecute`, `packages/nodes-base` ships ~560 built-in integration nodes, and a Vue 3 editor lives in `packages/frontend/editor-ui`. It persists state via TypeORM (`@n8n/db`) to SQLite or Postgres, scales horizontally in 'queue' mode with Redis/Bull workers and multi-main pub/sub, and is heavily modularized through a backend `*.module.ts` registry and frontend manifest system.

## System map

pnpm 12.4.2 / Turbo monorepo requiring Node >=24. The `n8n` binary (packages/cli/bin/n8n) loads config, then CommandRegistry dispatches a @Command-decorated class; `start` boots an Express Server (AbstractServer subclass), `worker` runs Bull consumers, `webhook` runs a dedicated webhook process. Execution is handled by n8n-core's WorkflowExecute; persistence is TypeORM in @n8n/db over SQLite/Postgres; the editor is a Vue 3 + Vite + Pinia SPA served from a generated static cache. Queue mode adds Redis pub/sub + Bull queues and optional multi-main leaders.
- Monorepo is managed by pnpm workspaces with Turbo task orchestration; packageManager is pinned to pnpm@12.4.2 and Node >=24 is required. — [package.json](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/package.json) [pnpm-workspace.yaml](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/pnpm-workspace.yaml) [turbo.json](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/turbo.json)
- Process entry is packages/cli/bin/n8n: it checks the Node version, loads dotenv, requires dist/config early (entity column types depend on GlobalConfig.database.type), then resolves CommandRegistry from the @n8n/di Container and calls execute(). — [packages/cli/bin/n8n#L38-63](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/bin/n8n#L38-L63)
- CommandRegistry maps argv[2] (default 'start') to a file under dist/commands, imports it so its @Command decorator registers metadata, parses flags with a per-command zod schema, then runs the class lifecycle init() -> run() -> catch() -> finally(). — [packages/cli/src/command-registry.ts#L29-104](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/command-registry.ts#L29-L104)
- packages/cli is the 'n8n' npm package: an Express server (Server extends AbstractServer), ~40 decorator-registered REST controllers, plus modules, scaling, webhooks, task-runner, and push subsystems, all wired through @n8n/di's Container.get(). — [packages/cli/src/server.ts#L34-85](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/server.ts#L34-L85) [packages/cli/package.json](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/package.json)
- n8n-core (packages/core) owns the execution engine: WorkflowExecute (3168-line file) drives node runs, alongside node-execution-contexts, partial-execution-utils, triggers-and-pollers, nodes-loader, and encryption. — [packages/core/src/execution-engine/workflow-execute.ts#L118-150](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/core/src/execution-engine/workflow-execute.ts#L118-L150) [packages/core/src/nodes-loader/directory-loader.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/core/src/nodes-loader/directory-loader.ts) [packages/core/src/encryption/cipher.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/core/src/encryption/cipher.ts)
- n8n-workflow (packages/workflow) holds the shared type model (INode, IRunData, IConnections), the Workflow class (nodes + connectionsBySourceNode/ByDestinationNode + expression), and the Tournament-based expression evaluator. — [packages/workflow/src/workflow.ts#L58-100](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/workflow/src/workflow.ts#L58-L100) [packages/workflow/src/expression-evaluator-proxy.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/workflow/src/expression-evaluator-proxy.ts)
- n8n-nodes-base ships built-in integrations: ~560 *.node.ts files under nodes/ and ~409 *.credentials.ts files under credentials/, enumerated in the package.json 'n8n.nodes'/'n8n.credentials' arrays that the DirectoryLoader consumes. — [packages/nodes-base/package.json#L35-36](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/nodes-base/package.json#L35-L36) [packages/nodes-base/nodes/Set/Set.node.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/nodes-base/nodes/Set/Set.node.ts)
- @n8n/db is the persistence layer: ~50 entities (WorkflowEntity, ExecutionEntity/ExecutionData, CredentialsEntity, WebhookEntity, User, Project, SharedWorkflow, Settings, DeploymentKey, ScheduledJob...) plus sqlite/postgres migration trees; DbConnection wraps the TypeORM DataSource and runs migrations under a Postgres advisory lock. — [packages/@n8n/db/src/entities/index.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/%40n8n/db/src/entities/index.ts) [packages/@n8n/db/src/connection/db-connection.ts#L140-185](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/%40n8n/db/src/connection/db-connection.ts#L140-L185)
- The editor frontend is a Vue 3 + Vite + Pinia app (n8n-editor-ui) that mounts App.vue with a router, i18n, telemetry/sentry plugins, and a set of manifest registration functions for module-provided UI. — [packages/frontend/editor-ui/src/main.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/frontend/editor-ui/src/main.ts)
- Deployment modes: 'regular' single process; 'queue' mode splits into main(s), workers, and webhook processes communicating over Redis pub/sub (Publisher/Subscriber/PubSubRegistry) with Bull job queues; multi-main is licensed and leader-elected via MultiMainSetup. — [packages/cli/src/commands/worker.ts#L80-168](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/worker.ts#L80-L168) [packages/cli/src/scaling/scaling.service.ts#L97-132](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/scaling/scaling.service.ts#L97-L132) [packages/cli/src/commands/start.ts#L231-247](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/start.ts#L231-L247)
- Backend functionality is split into 'modules': ModuleRegistry loads <name>.module.js for ~32 default modules (insights, external-secrets, community-packages, data-table, oauth-server, mcp, source-control, sso-oidc, sso-saml, ldap, chat-hub, instance-ai, ...), gated by ModulesConfig enabled/disabled lists. — [packages/@n8n/backend-common/src/modules/module-registry.ts#L44-120](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/%40n8n/backend-common/src/modules/module-registry.ts#L44-L120)
- Configuration is centralized in @n8n/config's GlobalConfig: config classes use @Env decorators mapping env vars (e.g. N8N_RUNNERS_MODE, N8N_RUNNERS_BROKER_PORT) onto typed fields. — [packages/@n8n/config/src/configs/runners.config.ts#L16-89](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/%40n8n/config/src/configs/runners.config.ts#L16-L89)
- Deployment artifacts: Dockerfiles under docker/images (n8n, n8n-base, runners, node-pc, engine) and root scripts build:docker/dockerize-n8n.mjs produce the shippable image. — [docker/images/n8n/Dockerfile](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/docker/images/n8n/Dockerfile) [package.json#L17-22](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/package.json#L17-L22)

## Feature catalog

User-facing surface = REST controllers + webhook endpoints + public API v1 + CLI subcommands + Vue editor. The workflows controller covers CRUD/activate/run/share/transfer/archive; executions controller covers list/stop/retry/delete; credential controllers include OAuth1/OAuth2 callback flows. Module-provided features (SSO, LDAP, source control, external secrets, insights, chat-hub, MCP, instance-ai) extend the surface. The editor is served as static assets with history-API fallback.
- Workflows REST API: POST /workflows, GET list with query middleware, GET /:id, PATCH, DELETE, archive/unarchive, activate/deactivate, POST /:id/run (manual execution), share, transfer, collaboration write-lock, publication-status, with-node-types. — [packages/cli/src/workflows/workflows.controller.ts#L71-707](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/workflows/workflows.controller.ts#L71-L707)
- Executions API: GET /executions (range-parsed), GET /:id, POST /:id/stop, POST /stopMany, POST /:id/retry, POST /delete, PATCH /:id, plus GET /versions/:workflowId. — [packages/cli/src/executions/executions.controller.ts#L22-143](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/executions/executions.controller.ts#L22-L143)
- Auth/user surface: auth.controller, me.controller, users.controller, owner.controller, invitation, password-reset, change-email, api-keys, mfa.controller (conditionally imported when MFA enabled), role.controller, security-settings. — [packages/cli/src/server.ts#L34-74](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/server.ts#L34-L74) [packages/cli/src/server.ts#L141-144](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/server.ts#L141-L144)
- Credential flows: credentials.controller plus oauth/oauth1-credential.controller and oauth2-credential.controller handle OAuth callback round-trips; CredentialsOverwrites exposes a preset-credentials endpoint gated by optional middleware. — [packages/cli/src/server.ts#L44-46](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/server.ts#L44-L46) [packages/cli/src/server.ts#L294-347](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/server.ts#L294-L347) [packages/cli/src/credentials/credentials.controller.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/credentials/credentials.controller.ts)
- Webhook surface: AbstractServer registers live webhooks, waiting webhooks, waiting forms, test webhooks, and Slack/Telegram interaction webhooks via createWebhookHandlerFor before bodyParser so binary payloads survive. — [packages/cli/src/abstract-server.ts#L241-295](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/abstract-server.ts#L241-L295) [packages/cli/src/webhooks/webhook-request-handler.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/webhooks/webhook-request-handler.ts)
- Public REST API v1 is loaded by loadPublicApiVersions and validated by express-openapi-validator against openapi.yml; routes live under public-api/v1 handlers. — [packages/cli/src/server.ts#L233-237](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/server.ts#L233-L237) [packages/cli/src/public-api/index.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/public-api/index.ts) [packages/cli/src/public-api/v1/openapi.yml](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/public-api/v1/openapi.yml)
- Real-time push: Push service picks WebSocketPush or SSEPush based on N8N_PUSH_BACKEND; bidirectional mode additionally enables the CollaborationService for collaborative editing. — [packages/cli/src/push/index.ts#L24-70](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/push/index.ts#L24-L70) [packages/cli/src/server.ts#L241-253](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/server.ts#L241-L253)
- CLI subcommands: start, worker, webhook, execute, execute-batch, export/import (workflows & credentials), db migrate/revert, ldap sync, license ops, mfa, publish/unpublish, update, user-management, audit, ttwf; module commands are also discovered via ModuleRegistry. — [packages/cli/src/command-registry.ts#L106-129](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/command-registry.ts#L106-L129) [packages/cli/src/commands/start.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/start.ts) [packages/cli/src/commands/worker.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/worker.ts)
- Community packages feature: CommunityPackagesService installs/updates/removes npm packages of community nodes using execFile'd npm commands with integrity checks against a vetted-package list. — [packages/cli/src/modules/community-packages/community-packages.service.ts#L18-49](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/modules/community-packages/community-packages.service.ts#L18-L49) [packages/cli/src/modules/community-packages/npm-utils.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/modules/community-packages/npm-utils.ts)
- Enterprise modules gate features behind the license: sso-saml, sso-oidc, ldap, source-control, external-secrets, log-streaming, provisioning, workflow-reviews, promotions are all registered in ModuleRegistry.defaultModules with LicenseState consulted at init. — [packages/@n8n/backend-common/src/modules/module-registry.ts#L44-80](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/%40n8n/backend-common/src/modules/module-registry.ts#L44-L80) [packages/cli/src/modules/external-secrets.ee/external-secrets.module.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/modules/external-secrets.ee/external-secrets.module.ts)
- AI features: @n8n/n8n-nodes-langchain nodes (agents, vector stores, tools), @n8n/ai-workflow-builder, @n8n/instance-ai ('n8n Assistant'), chat-hub module, and an MCP server (McpServer from n8n-nodes-langchain/mcp) with worker-relay handling in queue mode. — [packages/cli/src/commands/start.ts#L12](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/start.ts#L12) [packages/cli/src/commands/start.ts#L339-351](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/start.ts#L339-L351) [packages/cli/src/modules/instance-ai](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/modules/instance-ai)
- Evaluation/test-run feature: evaluation.ee controllers (test-runs, evaluation-config, evaluation-collections, eval-insights) plus TestRunCleanupService in base-command. — [packages/cli/src/server.ts#L66-69](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/server.ts#L66-L69) [packages/cli/src/commands/base-command.ts#L36](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/base-command.ts#L36)
- Task runners: Code-node execution is offloaded to spawned JS/Python task-runner processes brokered over a websocket (task-broker), configured by N8N_RUNNERS_* env vars. — [packages/cli/src/task-runners/task-runner-process-js.ts#L34-53](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/task-runners/task-runner-process-js.ts#L34-L53) [packages/@n8n/config/src/configs/runners.config.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/%40n8n/config/src/configs/runners.config.ts)
- Editor UI is generated at startup: Start.generateStaticAssets rewrites index.html/js/css placeholders (%CONFIG_TAGS%, BASE_PATH, REST_ENDPOINT) into instanceSettings.staticCacheDir, which Server serves via express.static + a historyApiHandler that injects per-request CSP nonces. — [packages/cli/src/commands/start.ts#L161-205](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/start.ts#L161-L205) [packages/cli/src/server.ts#L442-491](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/server.ts#L442-L491)

## Data flow

Workflow JSON and credentials live in @n8n/db entities; credentials are encrypted through Cipher (AES-256-CBC/GCM with key-id prefixes). Incoming REST/webhook requests hit controllers/handlers, which delegate to services and repositories; executions run through WorkflowRunner -> WorkflowExecute, producing IRunExecutionData that lifecycle hooks persist via ExecutionPersistence into execution_entity/execution_data. In queue mode the main enqueues a Bull job consumed by a worker which runs the same engine and reports back over Redis pub/sub. The editor reads/writes the same REST API and receives live updates via Push (ws/sse).
- Core persisted state: WorkflowEntity (nodes/connections/settings/pinData), CredentialsEntity (encrypted data), WebhookEntity (path->workflow/node mapping), ExecutionEntity + ExecutionData, plus Settings rows with loadOnStartup that are re-applied to the convict config at boot. — [packages/@n8n/db/src/entities/index.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/%40n8n/db/src/entities/index.ts) [packages/cli/src/commands/start.ts#L407-413](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/start.ts#L407-L413)
- Request path: Express middleware (cookie-parser, browserId extraction, CSP) -> AuthStrategyRegistry (ApiKeyAuthStrategy then SessionCookieAuthStrategy) -> ControllerRegistry-activated routes -> services -> repositories -> TypeORM DataSource. — [packages/cli/src/server.ts#L191-268](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/server.ts#L191-L268) [packages/cli/src/controller.registry.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/controller.registry.ts)
- Execution path: controller/manual trigger -> WorkflowRunner.run -> WorkflowExecute.run (PCancelable<IRun>) which iterates the execution stack, calls node execute functions via NodeExecuteFunctions contexts, and emits lifecycle hooks (workflowExecuteBefore/After, nodeExecuteBefore/After) that persist progress. — [packages/cli/src/workflow-runner.ts#L108-130](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/workflow-runner.ts#L108-L130) [packages/core/src/execution-engine/workflow-execute.ts#L142-150](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/core/src/execution-engine/workflow-execute.ts#L142-L150) [packages/cli/src/execution-lifecycle/execution-lifecycle-hooks.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/execution-lifecycle/execution-lifecycle-hooks.ts)
- Credential secrecy: Cipher.encryptV2 encrypts with the active key from EncryptionKeyProxy, emitting 'keyId:ciphertext' for rotated keys or bare ciphertext for the legacy no-prefix format; decryptV2 parses the prefix, looks up the DEK, unwraps it with the instance key (AES-256-GCM), then decrypts. — [packages/core/src/encryption/cipher.ts#L54-135](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/core/src/encryption/cipher.ts#L54-L135)
- Webhook data path: request -> WebhookRequestHandler.handleRequest (method check, CORS) -> webhookManager.executeWebhook -> WebhookService.findTriggerWebhooksByPath against webhook_entity -> workflow execution -> WebhookResponse (static/stream/no-response) -> headers via WebhookResponseHeaders. — [packages/cli/src/webhooks/webhook-request-handler.ts#L43-120](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/webhooks/webhook-request-handler.ts#L43-L120) [packages/cli/src/webhooks/webhook.service.ts#L120-218](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/webhooks/webhook.service.ts#L120-L218)
- Queue-mode data path: main's ScalingService.setupQueue creates a Bull queue (maxStalledCount: 0) named by resolveQueueName(instanceType, poolName); workers run setupWorker(concurrency) -> JobProcessor executes the same WorkflowExecute and relays status through Redis pub/sub channels (command, worker-response, mcp-relay). — [packages/cli/src/scaling/scaling.service.ts#L97-132](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/scaling/scaling.service.ts#L97-L132) [packages/cli/src/commands/worker.ts#L208-243](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/worker.ts#L208-L243) [packages/cli/src/commands/start.ts#L329-351](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/start.ts#L329-L351)
- Binary data flows through BinaryDataConfig/BinaryDataService with pluggable stores: filesystem managers under cli/binary-data plus S3/Azure object stores via @n8n/blob-storage (S3ByteStore, AzureByteStore). — [packages/cli/src/commands/base-command.ts#L13](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/base-command.ts#L13) [packages/cli/src/binary-data/binary-data-access.service.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/binary-data/binary-data-access.service.ts)
- Frontend bootstrap: index.html is pre-split around HTML_NONCE_PLACEHOLDER and served per-request with a fresh CSP nonce by historyApiHandler; REST endpoint + Sentry config reach the client as base64 meta tags injected during generateStaticAssets. — [packages/cli/src/server.ts#L442-485](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/server.ts#L442-L485) [packages/cli/src/commands/start.ts#L140-205](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/start.ts#L140-L205)
- Node/credential type metadata: LoadNodesAndCredentials scans node_modules for n8n-nodes-base and @n8n/n8n-nodes-langchain, plus custom dirs and module-provided nodeLoaders; built types are written to staticCacheDir (/types/nodes.json, credentials.json, node-versions.json) served behind auth. — [packages/cli/src/load-nodes-and-credentials.ts#L103-137](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/load-nodes-and-credentials.ts#L103-L137) [packages/cli/src/server.ts#L497-524](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/server.ts#L497-L524)
- Expression evaluation: workflow expressions are evaluated by @n8n/tournament (an AST interpreter, not eval) wrapped by expression-evaluator-proxy and hardened by expressionSandboxHooks in expression-sandboxing.ts. — [packages/workflow/src/expression-evaluator-proxy.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/workflow/src/expression-evaluator-proxy.ts) [packages/workflow/src/expression-sandboxing.ts#L568-572](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/workflow/src/expression-sandboxing.ts#L568-L572) [packages/@n8n/tournament/src/index.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/%40n8n/tournament/src/index.ts)
- Event bus: MessageEventBus is initialized per-process (workerId payload on workers) and LogStreamingEventRelay forwards events; execution/workflow lifecycle also feeds TelemetryEventRelay, ActivityEventRelay, and workflow-failure notifications. — [packages/cli/src/commands/worker.ts#L170-175](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/worker.ts#L170-L175) [packages/cli/src/commands/base-command.ts#L37-40](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/base-command.ts#L37-L40)

## Behavior traces

Three flows traced end-to-end. (1) Startup: bin/n8n -> CommandRegistry.execute -> Start.init (DB connect/migrate, license, community packages, roles, modules) -> Start.run (settings, server.start, schedulers, workflow activation). (2) Webhook: express route -> WebhookRequestHandler -> LiveWebhooks/WebhookService -> workflow execution -> WebhookResponse. (3) Execution: REST run -> WorkflowRunner -> WorkflowExecute -> lifecycle hooks -> DB persistence. Shutdown and worker boot are also documented.
- Startup order: bin/n8n (version check, dotenv, dist/config) -> Container.get(CommandRegistry).execute() -> imports commands/start.js -> parses flags via zod -> Container.get(Start) -> init() -> run(). — [packages/cli/bin/n8n#L60-63](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/bin/n8n#L60-L63) [packages/cli/src/command-registry.ts#L45-104](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/command-registry.ts#L45-L104)
- BaseCommand.init runs first inside Start.init: installOutboundProxyAgents -> ErrorReporter.init (Sentry) -> signal handlers -> LoadNodesAndCredentials.init -> optional RedisLockService -> DbConnection.init -> server.init -> DbConnection.migrate -> instanceSettings.initialize. — [packages/cli/src/commands/base-command.ts#L106-200](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/base-command.ts#L106-L200)
- Start.init continues: DeprecationService.warn -> PollJobProvider.init -> instance role resolution (regular=leader else initOrchestration) -> JwtService/BinaryDataConfig init -> initLicense -> ensureMultiMainLicensed -> initCommunityPackages -> PubSubRegistry.init (rewire) -> AuthRolesService.init + RoleCacheService.refreshCache (main only) -> BinaryDataService -> WaitTracker -> CredentialsOverwrites -> dedup -> external hooks -> workflow history -> generateStaticAssets -> moduleRegistry.initModules -> AuthHandlerRegistry -> MultiMainSetup handlers -> executionContextHookRegistry -> postProcessLoaders. — [packages/cli/src/commands/start.ts#L207-320](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/start.ts#L207-L320)
- Start.run: load SettingsRepository rows where loadOnStartup into convict config -> optional SQLite VACUUM -> server.start() -> init pruning/history-compaction/statistics services -> register mainSystemTasks + SystemTaskRunner -> DurableScheduler.start -> EnqueuedExecutionRecoveryService (regular mode) -> either workflow-publication outbox consumer or ActiveWorkflowManager.init -> releaseTypes -> print editor URL -> TTY 'o' key opens browser. — [packages/cli/src/commands/start.ts#L404-512](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/start.ts#L404-L512)
- Server.start -> configure(): Prometheus metrics (optional) -> frontendService.getSettings + external hook -> PostHog -> loadPublicApiVersions mounts public routers -> register auth strategies -> cookieParser -> CSP middleware (deliberately after webhook routes) -> Push.setupPushHandler -> CollaborationService if websocket -> ScalingService.setupQueue in queue mode -> registerAdditionalControllers -> PubSubRegistry.init -> ControllerRegistry.activate -> eventBus.initialize -> workflow indexing -> preset-credentials route -> protectTypeFiles -> static/history handlers. — [packages/cli/src/server.ts#L104-126](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/server.ts#L104-L126) [packages/cli/src/server.ts#L170-495](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/server.ts#L170-L495)
- Webhook request flow: route registered on AbstractServer before bodyParser -> createWebhookHandlerFor(LiveWebhooks|WaitingWebhooks|TestWebhooks|...) -> WebhookRequestHandler.handleRequest: reject non-HTTP-methods, CORS preflight handling, then webhookManager.executeWebhook -> response dispatched as WebhookResponse (no-response/static/stream) or legacy {data,responseCode,headers}. — [packages/cli/src/abstract-server.ts#L241-295](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/abstract-server.ts#L241-L295) [packages/cli/src/webhooks/webhook-request-handler.ts#L43-94](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/webhooks/webhook-request-handler.ts#L43-L94)
- Webhook resolution persists through WebhookService: findTriggerWebhooksByPath queries webhook_entity rows, pickMatchingTemplate selects the best segment match, storeWebhook inserts rows; on activation ActiveWorkflowManager registers live paths and deactivation calls deleteWorkflowWebhooks. — [packages/cli/src/webhooks/webhook.service.ts#L120-296](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/webhooks/webhook.service.ts#L120-L296) [packages/cli/src/active-workflow-manager.ts#L101-110](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/active-workflow-manager.ts#L101-L110)
- Manual/REST execution: POST /workflows/:workflowId/run (or executions retry) -> WorkflowRunner.run -> build additionalData via WorkflowExecuteAdditionalData -> pick lifecycle hooks (getLifecycleHooksForRegularMain/ScalingMain/ScalingWorker) -> new WorkflowExecute(...).run(...) returns PCancelable -> processRunExecutionData walks the execution stack -> nodeExecute functions resolve params, credentials, expressions -> hooks persist via ExecutionPersistence/saveExecutionProgress -> Push broadcasts progress to the editor. — [packages/cli/src/workflows/workflows.controller.ts#L506](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/workflows/workflows.controller.ts#L506) [packages/cli/src/workflow-runner.ts#L49-70](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/workflow-runner.ts#L49-L70) [packages/core/src/execution-engine/workflow-execute.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/core/src/execution-engine/workflow-execute.ts) [packages/cli/src/execution-lifecycle/execution-lifecycle-hooks.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/execution-lifecycle/execution-lifecycle-hooks.ts)
- Worker boot: 'worker' command forces executions.mode='queue' -> setConcurrency (env N8N_CONCURRENCY_PRODUCTION_LIMIT or --concurrency, warns <5) -> init chain (license, community packages, credentials overwrites, binary data, dedup, event bus) -> ScalingService.setupQueue -> Redis subscriber on command channel -> run() optionally starts WorkerServer (health/overwrites/metrics) then setupWorker(concurrency) consumes Bull jobs; process then idles forever. — [packages/cli/src/commands/worker.ts#L77-259](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/worker.ts#L77-L259)
- Graceful shutdown (Start.stopProcess): removeAllQueuedWorkflowActivations -> WaitTracker.stopTracking -> externalHooks 'n8n.stop' -> removeAllNonWebhookTriggerWorkflows -> MultiMainSetup.shutdown (if multi-main) -> Publisher/Subscriber.shutdown (queue mode) -> EventService 'instance-stopped' -> ActiveExecutions.shutdown -> MessageEventBus.close -> exitSuccessFully; SIGTERM/SIGINT handlers are installed in BaseCommand.init. — [packages/cli/src/commands/start.ts#L101-134](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/start.ts#L101-L134) [packages/cli/src/commands/base-command.ts#L153-154](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/base-command.ts#L153-L154)

## Testing signals

Unit/integration tests are Vitest, orchestrated by a workspace glob over every package's vitest.config; packages/cli alone has ~286 integration test files plus unit/migration suites runnable against SQLite or Postgres (incl. testcontainers). nodes-base tests live per-node and via JSON workflow harnesses. Playwright covers e2e UI, API, container, performance and benchmark projects under packages/testing/playwright. CI (ci-master.yml) builds, unit-tests on Node 24/26, lints, runs CodSpeed benchmarks, and verifies single-instance npm packaging; Stryker mutation testing is wired via the 'mutate' script.
- Vitest workspace at repo root collects every packages/**/vitest.config.*; ~6368 *.test.ts files exist across the repo. — [vitest.workspace.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/vitest.workspace.ts)
- packages/cli test scripts run vitest with DB_TYPE=sqlite by default; separate configs (vitest.config.integration.ts, vitest.config.migration.ts, *.testcontainers.ts) target integration and migration suites; test:postgres variants set DB_TYPE=postgresdb. — [packages/cli/package.json#L27-47](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/package.json#L27-L47) [packages/cli/test/integration](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/test/integration)
- Test layout in packages/cli/test: unit/, integration/, migration/, shared/, plus global-setup.ts and setup-testcontainers.ts helpers. — [packages/cli/test/global-setup.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/test/global-setup.ts) [packages/cli/test/setup-testcontainers.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/test/setup-testcontainers.ts)
- nodes-base keeps per-node tests in <Node>/test/ or __tests__ dirs plus a top-level test/ folder with JSON workflow harness tests (NodeTestHarness) and nock-based HTTP mocking. — [packages/nodes-base/test](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/nodes-base/test) [packages/nodes-base/nodes/Webhook/test/description.test.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/nodes-base/nodes/Webhook/test/description.test.ts) [packages/nodes-base/AGENTS.md](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/nodes-base/AGENTS.md)
- Playwright suite lives in packages/testing/playwright with playwright.config.ts + playwright-projects.ts; tests/e2e, tests/framework, infrastructure/benchmark and performance projects; currents.config.ts indicates Currents reporting. — [packages/testing/playwright/playwright.config.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/testing/playwright/playwright.config.ts) [packages/testing/playwright/tests/e2e](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/testing/playwright/tests/e2e) [packages/testing/playwright/currents.config.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/testing/playwright/currents.config.ts)
- CI on master (ci-master.yml): build-github job, unit-test reusable workflow on Node 24.18.1 and 26.5.1 with coverage on 24, lint reusable workflow, CodSpeed performance benchmarks post-merge, and verify-single-instance-npm packaging check. — [.github/workflows/ci-master.yml](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/.github/workflows/ci-master.yml)
- Mutation testing is available via Stryker: @stryker-mutator/core + vitest-runner in devDeps and 'pnpm mutate'/'mutate:diff' scripts. — [package.json#L71-72](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/package.json#L71-L72) [package.json#L101-102](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/package.json#L101-L102)
- Core engine has dedicated unit tests: workflow-execute.test.ts, routing-node.test.ts, active-workflow-triggers.test.ts under packages/core/src/execution-engine/__tests__. — [packages/core/src/execution-engine/__tests__/workflow-execute.test.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/core/src/execution-engine/__tests__/workflow-execute.test.ts)
- Encryption upgrades/rotations have dedicated Playwright suites exposed via root scripts test:encryption:upgrade and test:encryption:rotation. — [package.json#L82-83](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/package.json#L82-L83)
- Coverage is collected through @vitest/coverage-v8 and codecov.yml; Codecov token is threaded through ci-master build env. — [packages/cli/package.json#L125](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/package.json#L125) [codecov.yml](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/codecov.yml) [.github/workflows/ci-master.yml](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/.github/workflows/ci-master.yml)

## Risk map

Security-sensitive surface: community-package npm installs shell out via execFile; credential secrecy relies on Cipher key handling and N8N_ENCRYPTION_KEY; expression evaluation and JS/Python task runners execute user code (Tournament sandbox + prototype hardening + spawned processes); webhook/form routes intentionally serve user HTML outside the CSP middleware. The 3.1k-line WorkflowExecute and the multi-stage Start.init ordering are blast-radius hotspots, as are Redis pub/sub trust and the Postgres migration advisory lock.
- Community packages install third-party code at runtime: community-packages.service uses execFile/executeNpmCommand (npm pack/install) against registry.npmjs.org by default; verifyIntegrity and a vetted-package list mitigate but the attack surface (supply chain, postinstall) is inherent. — [packages/cli/src/modules/community-packages/community-packages.service.ts#L18-49](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/modules/community-packages/community-packages.service.ts#L18-L49) [packages/cli/src/modules/community-packages/npm-utils.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/modules/community-packages/npm-utils.ts)
- LoadNodesAndCredentials mutates global module resolution: it rewrites NODE_PATH and calls node's private module._initPaths() so community packages resolve — fragile across Node upgrades and a code-loading vector for anything on NODE_PATH. — [packages/cli/src/load-nodes-and-credentials.ts#L78-101](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/load-nodes-and-credentials.ts#L78-L101)
- Credential encryption depends on the instance key: Cipher.decryptV2 throws UnexpectedError('Encryption key not found') for unknown keyIds and silently treats unprefixed data as legacy CBC; wrong/missing N8N_ENCRYPTION_KEY renders all stored credentials unreadable. — [packages/core/src/encryption/cipher.ts#L96-135](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/core/src/encryption/cipher.ts#L96-L135) [packages/core/src/encryption/cipher.ts#L20](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/core/src/encryption/cipher.ts#L20)
- WorkflowExecute is a 3168-line stateful class; its run() must remain non-async to preserve PCancelable cancellation (explicit comment), meaning a well-meaning refactor breaks execution cancellation. — [packages/core/src/execution-engine/workflow-execute.ts#L138-150](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/core/src/execution-engine/workflow-execute.ts#L138-L150)
- Webhook/form routes are registered before bodyParser AND before the CSP middleware on purpose — user-authored HTML responses must not get the instance CSP; the comment warns that reordering 'silently changes' the security posture. — [packages/cli/src/server.ts#L212-227](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/server.ts#L212-L227) [packages/cli/src/abstract-server.ts#L241-295](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/abstract-server.ts#L241-L295)
- Preset-credentials endpoint (/endpointPresetCredentials) can be configured without the overwrite middleware; in that case credentials can only be set once per boot — a misconfiguration could leave a credential-injection route unauthenticated. — [packages/cli/src/server.ts#L294-347](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/server.ts#L294-L347)
- Expression evaluation is user-controlled code: Tournament evaluator + expressionSandboxHooks exist to contain it; any escape in the proxy/hooks layer is direct RCE inside the n8n process. (inferred) — [packages/workflow/src/expression-evaluator-proxy.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/workflow/src/expression-evaluator-proxy.ts) [packages/workflow/src/expression-sandboxing.ts#L568-572](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/workflow/src/expression-sandboxing.ts#L568-L572)
- JS task runners spawn 'node' subprocesses (JsTaskRunnerProcess.startProcess) with env-injected grant tokens; N8N_RUNNERS_INSECURE_MODE exists in config, and runners authenticate to a broker port — misconfiguration widens code-exec blast radius. — [packages/cli/src/task-runners/task-runner-process-js.ts#L34-53](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/task-runners/task-runner-process-js.ts#L34-L53) [packages/@n8n/config/src/configs/runners.config.ts#L24-89](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/%40n8n/config/src/configs/runners.config.ts#L24-L89) [packages/@n8n/task-runner/src/js-task-runner/prototype-hardening.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/%40n8n/task-runner/src/js-task-runner/prototype-hardening.ts)
- Auth: session cookie is a JWT (AUTH_COOKIE_NAME) bound to a browserId header and MFA-gated; ApiKeyAuthStrategy is evaluated before cookie auth so a wrong explicit key fails fast; revocation uses InvalidAuthToken entity. — [packages/cli/src/auth/auth.service.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/auth/auth.service.ts) [packages/cli/src/server.ts#L191-201](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/server.ts#L191-L201)
- Postgres migrations run inside an advisory lock scoped by schema+prefix hash with transaction:'none' — all pending migrations commit or roll back as one unit, so a mid-migration crash can leave a partially migrated schema for every co-starting instance. — [packages/@n8n/db/src/connection/db-connection.ts#L152-185](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/%40n8n/db/src/connection/db-connection.ts#L152-L185)
- Redis is a trust boundary in queue mode: commands flow over pub/sub channels to workers and webhooks relay through the same fabric; a compromised Redis can drive executions on all mains/workers. (inferred) — [packages/cli/src/commands/start.ts#L329-358](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/start.ts#L329-L358) [packages/cli/src/scaling/pubsub/subscriber.service.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/scaling/pubsub/subscriber.service.ts) [packages/cli/src/commands/worker.ts#L183-192](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/worker.ts#L183-L192)
- Startup ordering is contractually fragile: entities must be registered before DataSource creation (loadModules), PubSubRegistry.init() must be re-invoked after modules/community-packages register @OnPubSubEvent handlers, and JwtService/BinaryDataConfig init require DeploymentKeyRepository — comments flag each of these ordering constraints. — [packages/@n8n/backend-common/src/modules/module-registry.ts#L96-120](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/%40n8n/backend-common/src/modules/module-registry.ts#L96-L120) [packages/cli/src/commands/start.ts#L260-264](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/start.ts#L260-L264) [packages/cli/src/commands/start.ts#L455-462](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/start.ts#L455-L462)
- External secrets feature pulls secrets from AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, 1Password (SDK deps present); provider credentials and sync are handled in modules/external-secrets.ee — a leak here exposes all downstream credentials. — [packages/cli/package.json#L133-146](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/package.json#L133-L146) [packages/cli/src/modules/external-secrets.ee/external-secrets-manager.ee.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/modules/external-secrets.ee/external-secrets-manager.ee.ts)

## Extension points

n8n is built around registries. Backend: @Command files under commands/, @RestController classes activated by ControllerRegistry, *.module.ts backend modules via ModuleRegistry, NodeLoader implementations fed through LoadNodesAndCredentials (package dirs, custom dirs via CUSTOM_EXTENSION_ENV, module-provided loaders), @OnPubSubEvent handlers, and ExternalHooks. Nodes/credentials plug in via the package.json 'n8n' manifest. Frontend: manifest registration functions (modals, component slots, capabilities, upgrade redirects, module routes). Node types themselves expose loadOptions/listSearch/credentialTest/webhookMethods hooks.
- New CLI commands: create a class file under packages/cli/src/commands/, decorate with @Command({name, description, flagsSchema (zod)}); CommandRegistry imports commands/<name>.js by convention and runs init/run/catch/finally. — [packages/cli/src/command-registry.ts#L45-104](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/command-registry.ts#L45-L104) [packages/cli/src/commands/start.ts#L60-66](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/start.ts#L60-L66)
- New REST endpoints: classes decorated @RestController('/path') with @Get/@Post/@Put/@Patch/@Delete methods are activated en masse by ControllerRegistry.activate(app); most are side-effect-imported at the top of server.ts. — [packages/cli/src/controller.registry.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/controller.registry.ts) [packages/cli/src/server.ts#L34-74](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/server.ts#L34-L74) [packages/cli/src/executions/executions.controller.ts#L22](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/executions/executions.controller.ts#L22)
- New backend module: a <name>.module.ts under packages/cli/src/modules (or packages/modules) is discovered by ModuleRegistry.loadModules; modules can register entities, node loaders, commands, pubsub handlers; membership is controlled by defaultModules + ModulesConfig enabled/disabled. — [packages/@n8n/backend-common/src/modules/module-registry.ts#L44-120](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/%40n8n/backend-common/src/modules/module-registry.ts#L44-L120)
- New integration node: implement INodeType (execute or declarative requestDefaults; poll/trigger/webhook/webhookMethods for triggers) under nodes/<Service>/, add the dist path to package.json 'n8n.nodes', and credentials under credentials/ with 'n8n.credentials' entries. — [packages/nodes-base/package.json#L35-36](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/nodes-base/package.json#L35-L36) [packages/nodes-base/nodes/Set/Set.node.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/nodes-base/nodes/Set/Set.node.ts) [packages/nodes-base/AGENTS.md](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/nodes-base/AGENTS.md)
- Community/custom node packages: LoadNodesAndCredentials supports DirectoryLoader, PackageDirectoryLoader, LazyPackageDirectoryLoader and CustomDirectoryLoader; CUSTOM_EXTENSION_ENV adds custom directories; module nodeLoaders are appended via moduleRegistry.nodeLoaders. — [packages/cli/src/load-nodes-and-credentials.ts#L14-30](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/load-nodes-and-credentials.ts#L14-L30) [packages/cli/src/load-nodes-and-credentials.ts#L103-137](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/load-nodes-and-credentials.ts#L103-L137)
- Cross-process hooks: @OnPubSubEvent/@OnLeaderTakeover/@OnLeaderStepdown/@OnShutdown decorators register handlers that PubSubRegistry.init() wires to Redis channels — but init must be re-called after modules load (explicit rewire comments in start.ts). — [packages/cli/src/commands/start.ts#L260-264](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/start.ts#L260-L264) [packages/cli/src/scaling/pubsub/pubsub.registry.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/scaling/pubsub/pubsub.registry.ts) [packages/cli/src/active-workflow-manager.ts#L6](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/active-workflow-manager.ts#L6)
- ExternalHooks.run('n8n.stop', 'frontend.settings', 'activeWorkflows.initialized', ...) lets deployments inject custom logic via EXTERNAL_HOOKS config. — [packages/cli/src/commands/start.ts#L110](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/start.ts#L110) [packages/cli/src/server.ts#L179](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/server.ts#L179) [packages/cli/src/external-hooks.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/external-hooks.ts)
- Frontend extension manifests: registerModuleRoutes(router), registerEagerModals, registerComponentSlots, registerShellCapabilities, registerUpgradeRedirectGuard in main.ts — frontend modules under packages/modules/<name>/frontend plug into the editor shell through these manifests. — [packages/frontend/editor-ui/src/main.ts#L51-65](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/frontend/editor-ui/src/main.ts#L51-L65) [packages/frontend/editor-ui/src/app/moduleInitializer/moduleInitializer.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/frontend/editor-ui/src/app/moduleInitializer/moduleInitializer.ts) [packages/frontend/editor-ui/src/app/modals.manifest.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/frontend/editor-ui/src/app/modals.manifest.ts)
- Auth is a strategy chain: AuthStrategyRegistry evaluates registered strategies in order (ApiKeyAuthStrategy first, SessionCookieAuthStrategy last); modules 'can append later during their own module initialization' (e.g. token-exchange JWT). — [packages/cli/src/server.ts#L191-201](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/server.ts#L191-L201) [packages/cli/src/services/auth-strategy.registry.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/services/auth-strategy.registry.ts)
- Node dynamic behavior hooks: methods.loadOptions, methods.listSearch, methods.credentialTest and resourceMapping let nodes serve dynamic UI data without a dedicated endpoint; DynamicNodeParametersController exposes them over REST. — [packages/nodes-base/AGENTS.md](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/nodes-base/AGENTS.md) [packages/cli/src/server.ts#L41](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/server.ts#L41)
- Execution context hooks: ExecutionContextHookRegistry (init'ed at the end of Start.init) lets modules hook execution-context establishment; establishExecutionContext in core consumes them. — [packages/cli/src/commands/start.ts#L318-319](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/start.ts#L318-L319) [packages/core/src/execution-engine/execution-context.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/core/src/execution-engine/execution-context.ts)
- System tasks: mainSystemTasks(globalConfig) returns task classes registered into SystemTaskMetadata and provisioned by SystemTaskRunner on the DurableScheduler — a registry for durable background jobs. — [packages/cli/src/commands/start.ts#L429-437](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/start.ts#L429-L437) [packages/cli/src/scheduling/durable-scheduler.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/scheduling/durable-scheduler.ts)

## Contributor handoff

Use pnpm everywhere; build with turbo and redirect output (pnpm build > build.log 2>&1). All backend services are @n8n/di singletons resolved via Container.get; commands follow init/run/catch/finally; controllers/modules are decorator-registered. Respect hard ordering constraints (module entity registration before DataSource, PubSubRegistry re-init, no-async on WorkflowExecute.run, CSP middleware ordering). Test with per-package vitest; run lint+typecheck inside the touched package. TypeORM must stay inside @n8n/db or *.entity.ts/*.repository.ts files.
- Always use pnpm (npm install is blocked by a preinstall script); build via turbo ('pnpm build'), test via 'pnpm test' per package, lint via 'pnpm lint' (oxlint + eslint guardrails in cli), typecheck via 'pnpm typecheck'. — [package.json#L12-13](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/package.json#L12-L13) [packages/cli/package.json#L20-23](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/package.json#L20-L23)
- Dependency injection: services are @Service()-decorated classes resolved with Container.get(X); decorators (@Command, @RestController, @OnPubSubEvent, @Memoized, @Env) from @n8n/decorators drive registration — new code should follow this pattern rather than manual wiring. — [packages/cli/bin/n8n#L60-63](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/bin/n8n#L60-L63) [packages/cli/src/commands/start.ts#L60-69](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/start.ts#L60-L69) [packages/@n8n/di/src/di.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/%40n8n/di/src/di.ts)
- Do not add 'async' to WorkflowExecute.run — it must return PCancelable for cancellation; this is an explicit in-code contract. — [packages/core/src/execution-engine/workflow-execute.ts#L138-141](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/core/src/execution-engine/workflow-execute.ts#L138-L141)
- DB access boundary (per AGENTS.md): business logic must not import @n8n/typeorm; queries live in repositories under @n8n/db or a module's database/ folder; transactions use the TransactionRunner port, not .manager.transaction. — [AGENTS.md](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/AGENTS.md) [packages/@n8n/db/src/connection/db-connection.ts#L49-52](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/%40n8n/db/src/connection/db-connection.ts#L49-L52)
- Encryption boundary (per AGENTS.md): new code encrypts only via cipher.encryptV2/decryptV2; raw AES classes stay in packages/core/src/encryption; deployment keys are deactivated, never deleted. — [AGENTS.md](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/AGENTS.md) [packages/core/src/encryption/cipher.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/core/src/encryption/cipher.ts)
- Config changes go through @n8n/config config classes with @Env decorators — adding a setting means editing a *.config.ts (e.g. runners.config.ts), not reading process.env ad hoc; db type must be known before entities load (see bin/n8n comment). — [packages/@n8n/config/src/configs/runners.config.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/%40n8n/config/src/configs/runners.config.ts) [packages/cli/bin/n8n#L43-46](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/bin/n8n#L43-L46)
- After adding module-level @OnPubSubEvent handlers or node loaders, remember the rewire step: PubSubRegistry.init() is re-invoked after initCommunityPackages and after module init in both start.ts and worker.ts. — [packages/cli/src/commands/start.ts#L260-264](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/start.ts#L260-L264) [packages/cli/src/commands/worker.ts#L162-164](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/worker.ts#L162-L164)
- To run a single test file: cd into the package dir and 'pnpm test <file>'; cli integration tests need the vitest.config.integration.ts config and run on SQLite by default (DB_TYPE env switches dialect). — [AGENTS.md](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/AGENTS.md) [packages/cli/package.json#L27-40](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/package.json#L27-L40)
- Frontend work: editor-ui aliases '@/' to src; new feature modules belong under packages/modules/<name>/frontend (not editor-ui/src/features) and integrate via the manifest functions in main.ts; design-system components live in @n8n/design-system. — [packages/frontend/editor-ui/src/main.ts#L51-65](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/frontend/editor-ui/src/main.ts#L51-L65) [packages/frontend/AGENTS.md](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/frontend/AGENTS.md) [packages/modules](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/modules)
- Read first for most tasks: AGENTS.md (conventions), packages/cli/src/commands/base-command.ts + start.ts (init ordering), packages/cli/src/server.ts (middleware/controller wiring), packages/@n8n/backend-common/src/modules/module-registry.ts (module system), packages/core/src/execution-engine/workflow-execute.ts (execution engine). — [AGENTS.md](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/AGENTS.md) [packages/cli/src/commands/start.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/commands/start.ts) [packages/cli/src/server.ts](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/server.ts)
- Trap: command name 'executeBatch' is aliased to 'execute-batch' at runtime; commands whose files fail to import surface a real error while missing files fall through to module-provided commands. — [packages/cli/src/command-registry.ts#L35-57](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/packages/cli/src/command-registry.ts#L35-L57)
- V3 development: normal feature work lands on master behind opt-in flags; breaking changes only on the 3.x branch (per AGENTS.md reference to .github/DEVELOPING_V3.md). — [AGENTS.md](https://github.com/n8n-io/n8n/blob/1a370b965c1fe438dfa58f371a01080eb9cca776/AGENTS.md)

Source repository: https://github.com/n8n-io/n8n
Full evidence corpus: https://github.com/Codevetter/codevetter/tree/main/benchmarks/repo-unpacks/n8n.json

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
