This page documents the tooling and scripts that compile, assemble, and package the n8n monorepo: the Turborepo pipeline, pnpm workspace configuration, per-package build strategies, dependency pinning and patching, the setup-nodejs CI action, and the build-n8n.mjs production packaging script.
For details on CI workflow execution that consumes these artifacts, see CI Workflows. For Docker image construction, see Docker Image Pipeline. For the development environment setup, see Development Environment Setup.
The monorepo is managed with pnpm (version 10.22.0, enforced via packageManager field) and Turborepo (version 2.8.9). pnpm is enforced as the sole package manager via the root preinstall script, which runs scripts/block-npm-install.js to reject npm install invocations.
Workspace globs (pnpm-workspace.yaml):
| Glob | Contents |
|---|---|
packages/* | Core backend packages (cli, core, workflow, nodes-base, node-dev) |
packages/@n8n/* | Scoped internal packages (config, db, decorators, task-runner, etc.) |
packages/frontend/** | Frontend packages (editor-ui, design-system, chat, etc.) |
packages/extensions/** | Extension packages |
packages/testing/** | Testing infrastructure (playwright, containers) |
Sources: pnpm-workspace.yaml1-6 package.json8-9
Turborepo orchestrates the build by reading a turbo.json at the repository root. It resolves the topological order of packages based on package.json dependencies/devDependencies and runs tasks in parallel where the graph allows.
Root-level Turborepo scripts (from package.json):
| Script | Command |
|---|---|
build | turbo run build |
typecheck | turbo typecheck |
lint | turbo run lint |
clean | turbo run clean |
watch | turbo run watch --concurrency=30 |
test | turbo run test |
test:ci | turbo run test --continue --concurrency=1 |
Turborepo caches task outputs in per-package .turbo/ directories. The clean task (each package runs rimraf dist .turbo) removes both the compiled output and the Turborepo cache.
Sources: package.json13-51
Turborepo build dependency order (simplified)
Sources: packages/cli/package.json96-199 packages/core/package.json42-83 packages/workflow/package.json53-72 packages/nodes-base/package.json1-11
Each package defines its own build script. Turborepo calls each one in dependency order.
Build tool mapping
Sources: packages/cli/package.json7-11 packages/core/package.json13-16 packages/workflow/package.json22-27 packages/nodes-base/package.json7-11 packages/@n8n/nodes-langchain/package.json30-31 packages/frontend/editor-ui/package.json7-11 packages/frontend/@n8n/design-system/package.json9-11 packages/@n8n/config/package.json5-8 packages/@n8n/task-runner/package.json4-10
packages/core exposes four CLI binaries used during the n8n-nodes-base and @n8n/n8n-nodes-langchain builds:
| Binary | Purpose |
|---|---|
n8n-copy-static-files | Copies non-TypeScript assets (icons, templates) into dist/ |
n8n-generate-translations | Generates node translation JSON files |
n8n-generate-metadata | Generates node metadata files for the UI |
n8n-generate-node-defs | Generates node definition files |
Sources: packages/core/package.json7-11 packages/nodes-base/package.json7-11 packages/@n8n/nodes-langchain/package.json30-31
n8n-workflow Dual Module Formatn8n-workflow produces both ESM and CJS outputs so it can be consumed by both the Node.js backend (CJS) and the Vite-based frontend (ESM):
| Export path | Format | Entry file |
|---|---|---|
. (import) | ESM | dist/esm/index.js |
. (require) | CJS | dist/cjs/index.js |
./common (import) | ESM | dist/esm/common/index.js |
./common (require) | CJS | dist/cjs/common/index.js |
Sources: packages/workflow/package.json6-21
pnpm-workspace.yaml declares a default catalog and named catalogs so all packages reference a single pinned version without repeating it in every package.json. Individual packages reference these with the catalog: or catalog:<name> specifier.
| Catalog name | Purpose | Example entries |
|---|---|---|
default (anonymous catalog:) | Shared backend/common deps | typescript, lodash, zod, vitest, vite |
frontend | Vue/frontend-specific deps | vue, pinia, element-plus, vue-router |
storybook | Storybook tooling | storybook, @storybook/vue3-vite |
e2e | Playwright test tooling | @playwright/test, @currents/playwright |
sentry | Sentry SDKs | @sentry/node, @sentry/profiling-node |
Sources: pnpm-workspace.yaml8-128
The root package.json's pnpm.overrides section forces specific versions of transitive dependencies across the entire tree. Examples:
| Override | Reason |
|---|---|
typescript: 5.9.2 | Locks the TypeScript version monorepo-wide |
zod: 3.25.67 | Prevents multiple Zod versions coexisting |
axios: 1.13.5 | Security or compatibility pin |
esbuild: ^0.25.0 | Ensures consistent bundler version |
[email protected]: npm:[email protected] | Replaces a dependency with a fork |
Sources: package.json89-148
The pnpm.patchedDependencies field in package.json applies local patches to third-party packages at install time. Patches live in the patches/ directory.
| Package | Patch file |
|---|---|
[email protected] | patches/[email protected] |
[email protected] | patches/[email protected] |
[email protected] | patches/[email protected] |
@lezer/highlight | patches/@lezer__highlight.patch |
js-base64 | patches/js-base64.patch |
[email protected] | patches/[email protected] |
@types/[email protected] | patches/@[email protected] |
v-code-diff | patches/v-code-diff.patch |
Sources: package.json149-165
setup-nodejs GitHub ActionThe composite action at .github/actions/setup-nodejs is the standard build step used across all CI and release workflows. It handles:
turbo run build)Usage patterns across workflows:
Sources: .github/workflows/ci-pull-requests.yml78-81 .github/workflows/ci-pull-requests.yml106-113 .github/workflows/release-publish.yml40-46 .github/workflows/release-publish.yml68-72 .github/workflows/docker-build-push.yml88-95
scripts/build-n8n.mjsThe build:n8n root script (node scripts/build-n8n.mjs) is used for production and Docker image creation. It differs from the development turbo run build in that it also prunes dev dependencies and assembles a self-contained deployment directory.
The script performs these steps in sequence:
compiled/ and dist/ outputpnpm install and then turbo run build across all packagespackage.json files to remove devDependenciescompiled/ ā creates a pruned, production-only copy of the n8n package and its runtime dependenciesThe root package.json exposes three related scripts:
| Script | Command |
|---|---|
build:n8n | node scripts/build-n8n.mjs |
build:deploy | node scripts/build-n8n.mjs (alias) |
build:docker | node scripts/build-n8n.mjs && node scripts/dockerize-n8n.mjs |
build:docker:coverage | BUILD_WITH_COVERAGE=true node scripts/build-n8n.mjs && node scripts/dockerize-n8n.mjs |
Sources: scripts/build-n8n.mjs1-15 package.json14-19
Production artifact flow
Sources: scripts/build-n8n.mjs1-15 docker/images/n8n/Dockerfile14-22 package.json14-20
The docker/images/n8n/Dockerfile expects the compiled/ directory to already exist (produced by scripts/build-n8n.mjs) before the Docker build context is invoked. During the Docker build:
COPY ./compiled /usr/local/lib/node_modules/n8n ā installs the n8n packagenpm rebuild sqlite3 ā rebuilds the native SQLite bindings for the target architectureln -s /usr/local/lib/node_modules/n8n/bin/n8n /usr/local/bin/n8n ā creates the CLI symlinkThe base image is n8nio/base:<NODE_VERSION> (built from docker/images/n8n-base/Dockerfile), which provides Alpine Linux with git, tini, graphicsmagick, and other OS dependencies.
Sources: docker/images/n8n/Dockerfile1-32 docker/images/n8n-base/Dockerfile1-35
The @n8n/task-runner package (JavaScript sandbox process) is built separately and also packaged into the runners Docker image (docker/images/runners/Dockerfile). Its build output lands in dist/task-runner-javascript/ and is copied into the runners image.
The runners Dockerfile has two stages:
javascript-runner-builder): Takes the pre-built JS runner artifact, strips catalog: and workspace: references from package.json, then adds [email protected] for backwards compatibility.python-runner-builder): Builds the Python runner using uv into an isolated venv.Sources: docker/images/runners/Dockerfile1-78 packages/@n8n/task-runner/package.json4-18
During development, each package's watch script uses tsc-watch to recompile incrementally on file changes. The root watch script fans out to all packages via Turborepo:
turbo run watch --concurrency=30
Common development entry points from the root package.json:
| Script | What runs |
|---|---|
dev | All packages except @n8n/design-system, @n8n/chat, @n8n/task-runner |
dev:be | Backend only (excludes n8n-editor-ui) |
dev:fe | Frontend editor UI only |
dev:ai | @n8n/nodes-langchain, n8n, n8n-core |
dev:e2e | Playwright in UI mode |
Most backend packages define dev as an alias for watch, which runs tsc-watch -p tsconfig.build.json --onCompilationComplete "tsc-alias -p tsconfig.build.json".
The packages/cli package also has dev:worker and dev:webhook scripts that start the corresponding process alongside the TypeScript watcher using concurrently.
Sources: package.json21-26 packages/cli/package.json13-15 packages/core/package.json17-18
Before a release PR is created, .github/scripts/bump-versions.mjs updates version fields across all package.json files in the workspace. The script uses semver to increment versions according to the release type (minor, patch, major, experimental). This ensures all workspace packages stay on the same version number (currently 2.10.0).
The changelog is then regenerated by .github/scripts/update-changelog.mjs using conventional-changelog, reading commit history to produce an entry in CHANGELOG.md.
Sources: .github/scripts/bump-versions.mjs1-30 .github/scripts/update-changelog.mjs1-20 .github/workflows/release-create-pr.yml77-84
Refresh this wiki
This wiki was recently refreshed. Please wait 2 days to refresh again.