n8n-io/n8n
github.com/n8n-io/n8n ↗ · 3998 files · 17.0 MB · scan capped
n8n – The Platform for AI Agents and Workflow Automation Fair-code platform to build and deploy AI agents and workflows.
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.
- 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().
- 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 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().
- 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.
- 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.
- 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.
- @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.
- 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.
- 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.
- 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.
- 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.
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.
- Executions API: GET /executions (range-parsed), GET /:id, POST /:id/stop, POST /stopMany, POST /:id/retry, POST /delete, PATCH /:id, plus GET /versions/:workflowId.
- 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.
- 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.
- 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.
- Public REST API v1 is loaded by loadPublicApiVersions and validated by express-openapi-validator against openapi.yml; routes live under public-api/v1 handlers.
- Real-time push: Push service picks WebSocketPush or SSEPush based on N8N_PUSH_BACKEND; bidirectional mode additionally enables the CollaborationService for collaborative editing.
- 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.
- 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.
- 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.
- 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.
- Evaluation/test-run feature: evaluation.ee controllers (test-runs, evaluation-config, evaluation-collections, eval-insights) plus TestRunCleanupService in base-command.
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.
- Request path: Express middleware (cookie-parser, browserId extraction, CSP) -> AuthStrategyRegistry (ApiKeyAuthStrategy then SessionCookieAuthStrategy) -> ControllerRegistry-activated routes -> services -> repositories -> TypeORM DataSource.
- 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.
- 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.
- 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.
- 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).
- 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).
- 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.
- 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.
- 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.
- 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.
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().
- 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.
- 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.
- 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.
- 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.
- 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}.
- 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.
- 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.
- 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.
- 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.
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.
- 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.
- Test layout in packages/cli/test: unit/, integration/, migration/, shared/, plus global-setup.ts and setup-testcontainers.ts helpers.
- 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.
- 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.
- 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.
- Mutation testing is available via Stryker: @stryker-mutator/core + vitest-runner in devDeps and 'pnpm mutate'/'mutate:diff' scripts.
- 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__.
- Encryption upgrades/rotations have dedicated Playwright suites exposed via root scripts test:encryption:upgrade and test:encryption:rotation.
- Coverage is collected through @vitest/coverage-v8 and codecov.yml; Codecov token is threaded through ci-master build env.
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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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)
- 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.
- 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.
- 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.
- 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)
- 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.
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.
- 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.
- 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.
- 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.
- Community/custom node packages: LoadNodesAndCredentials supports DirectoryLoader, PackageDirectoryLoader, LazyPackageDirectoryLoader and CustomDirectoryLoader; CUSTOM_EXTENSION_ENV adds custom directories; module nodeLoaders are appended via moduleRegistry.nodeLoaders.
- 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).
- ExternalHooks.run('n8n.stop', 'frontend.settings', 'activeWorkflows.initialized', ...) lets deployments inject custom logic via EXTERNAL_HOOKS config.
- 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.
- 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).
- 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.
- Execution context hooks: ExecutionContextHookRegistry (init'ed at the end of Start.init) lets modules hook execution-context establishment; establishExecutionContext in core consumes them.
- System tasks: mainSystemTasks(globalConfig) returns task classes registered into SystemTaskMetadata and provisioned by SystemTaskRunner on the DurableScheduler — a registry for durable background jobs.
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'.
- 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.
- Do not add 'async' to WorkflowExecute.run — it must return PCancelable for cancellation; this is an explicit in-code contract.
- 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.
- 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.
- 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).
- 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.
- 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).
- 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.
- 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).
- 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.
- 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).
Languages
| Language | Files | Share of source |
|---|---|---|
| TypeScript | 21195 | 92% |
| JSON | 3831 | docs/config |
| Vue | 1308 | 6% |
| Markdown | 687 | docs/config |
| YAML | 384 | docs/config |
| JavaScript | 359 | 2% |
| Sass | 100 | 0% |
| Python | 69 | 0% |
Start reading here
Probable source entrypoint: packages/ — inferred from the directory tree and manifest, not the upstream docs.
Source roots
packages/ (3198 files)
Repository structure
| Top-level path | Files |
|---|---|
packages/ | 3198 |
docs/ | 284 |
.github/ | 229 |
scripts/ | 89 |
.agents/ | 64 |
.claude/ | 34 |
patches/ | 21 |
.devcontainer/ | 20 |
Docs found: AGENTS.mdCLAUDE.mdCONTRIBUTING.mdLICENSE.mdREADME.mddocs/db.mddocs/generated/postgres-schema/README.mddocs/generated/postgres-schema/public.activity_event.mddocs/generated/postgres-schema/public.agent_background_job.mddocs/generated/postgres-schema/public.agent_channel_status.md
Package
package.json — n8n-monorepo v2.41.0, 23 declared dependencies.
@biomejs/biome@dotenvx/dotenvx@n8n/eslint-config@n8n/module-cli@stryker-mutator/core@stryker-mutator/vitest-runner@types/node@types/supertestcross-enveslintkniplefthook
CI & config entrypoints
README.md— Repository readmeAGENTS.md— Agent instructionsCLAUDE.md— Claude instructions.github/workflows/backport.yml— GitHub Actions workflow.github/workflows/build-base-image.yml— GitHub Actions workflow.github/workflows/build-benchmark-image.yml— GitHub Actions workflow.github/workflows/build-node-pc-image.yml— GitHub Actions workflow.github/workflows/build-v3-nightly.yml— GitHub Actions workflow.github/workflows/build-windows.yml— GitHub Actions workflow.github/workflows/ci-check-pr-title.yml— GitHub Actions workflow
Structural health signals
Average score 7/10 across 96 analyzed source files; 16 hotspots surfaced.
packages/@n8n/typeorm/test/functional/repository/basic-methods/repository-basic-methods.ts— 922 lines, score 4 (Large file, Deep nesting)packages/@n8n/typeorm/test/integration/sample3-many-to-one.ts— 581 lines, score 5.5 (Large file, Deep nesting)packages/@n8n/typeorm/test/integration/sample4-many-to-many.ts— 540 lines, score 5.5 (Large file, Deep nesting)
These are deterministic review leads — size, churn, and structural signals — not proof of a bug in this project.
Recent history
1a370b965c1f2026-09-22 — feat(editor): Support external dropdown menu input (no-changelog) (#39188)5b149495c56a2026-09-22 — feat(ai-builder): Grade token consumption and memory compaction in Instance AI evals (no-changelog) (#38974)340d36c860df2026-09-22 — refactor(editor): Relocate MCP-shared constants and test helper (no-changelog) (#39196)870880963ddd2026-09-22 — feat(Databricks Node): Add the Pipeline resource to the hidden Databricks Trigger (no-changelog) (#38827)1308aae1b79f2026-09-22 — feat(Databricks Node): Poll pipeline update events for the Databricks Trigger (no-changelog) (#38842)