PNPM Monorepo Dependency Management#
Dify's frontend is a pnpm monorepo with a centralized dependency catalog defined in pnpm-workspace.yaml at the repo root. All shared dependency versions are pinned in one place; individual package.json files reference them with the "catalog:" specifier instead of version strings.
Workspace Structure#
The workspace includes five package groups :
| Entry | Path |
|---|---|
| Main web app | web/ |
| End-to-end tests | e2e/ |
| Node.js SDK | sdks/nodejs-client/ |
| Shared internal packages | packages/* |
| CLI | cli/ |
Internal monorepo packages reference each other with "workspace:*" (e.g., "@dify/contracts": "workspace:*" in web/package.json) , while all external npm dependencies use "catalog:" .
How the Catalog Works#
pnpm-workspace.yaml key settings#
The pnpm-workspace.yaml file drives the entire system:
catalogMode: preferβ tells pnpm to prefer catalog-defined versions when resolving dependencies. This was introduced in PR #34487.saveExact: trueβ locks catalog entries to exact versions (no^or~drift).catalog:block β defines 200+ dependencies with explicit, exact versions. Examples:react: 19.2.8,typescript: npm:@typescript/typescript6@6.0.2,vite: npm:@voidzero-dev/vite-plus-core@0.3.0.overrides:block β force-resolves transitive dependencies to safe versions (e.g., security patches liketar,ws,postcss).dedupeDirectDeps: trueβ deduplicates direct dependencies across the workspace.verifyDepsBeforeRun: installβ re-installs dependencies before running scripts if the lockfile is out of sync.engineStrict: trueβ enforces the Node.js engine constraint (^24.20.0) declared in the rootpackage.json. The rootpackage.jsonspecifiespnpm@11.25.0as the required package manager.
package.json usage pattern#
Any package in the workspace declares its dependencies with "catalog:" as the version:
// web/package.json (excerpt)
"react": "catalog:",
"zustand": "catalog:",
"@monaco-editor/react": "catalog:"
pnpm resolves these references to the exact versions in the catalog: block of pnpm-workspace.yaml at install time, producing a single pnpm-lock.yaml at the repo root.
npm aliases in the catalog#
Some catalog entries redirect to alternate packages via npm: aliases :
typescriptβnpm:@typescript/typescript6@6.0.2(uses the TS6 pre-release)viteβnpm:@voidzero-dev/vite-plus-core@0.3.0(custom Vite fork)
Adding or Updating a Dependency#
- New dependency: Add an entry to the
catalog:block inpnpm-workspace.yaml, then reference it as"catalog:"in the targetpackage.json. - Version bump: Change the version in
pnpm-workspace.yamlonly β all packages referencing"catalog:"pick it up automatically on next install. - Workspace-local package: Use
"workspace:*"instead of"catalog:"to always link the local build.
β οΈ Do not pin a version directly in a
package.jsonfile unless the package intentionally needs a version different from the catalog. Theeslint-plugin-pnpmlinter (in the catalog itself) enforces consistent catalog usage.
History#
The pnpm workspace was first introduced in PR #34241 (2026-03-30). The catalogMode: prefer setting was added shortly after in PR #34487 (2026-04-03). The catalog is actively maintained β see PR #39292 (2026-07-20) for a recent dependency sweep.
Knip Configuration#
Knip (dead code analysis) is owned and configured at the repository root. The root package.json contains the knip scripts (knip, knip:production, knip:production-unused-check), and the root knip.config.ts uses Knip's monorepo workspace model to configure the web workspace. This allows Knip to validate catalog references by seeing the root pnpm-workspace.yaml.
Key Files#
| File | Purpose |
|---|---|
pnpm-workspace.yaml | Single source of truth for dependency versions, workspace members, overrides |
package.json (root) | Root package scripts (including knip), engine constraints, packageManager field |
knip.config.ts (root) | Knip configuration with monorepo workspace support |
web/package.json | Main app's dependency list β all use "catalog:" |