Using pnpm Catalogs to Manage Dependency Versions in a Monorepo
Learn how pnpm catalogs let you centralize dependency versions in a monorepo, avoid phantom dependencies, and safely update packages across workspaces.
18 Jul 2025, 06:14 UTC

The problem: keeping versions in sync across many packages
In a typical monorepo you might have dozens of workspace packages that all depend on the same libraries, such as React or Lodash. Updating a version often means editing package.json in each workspace, or relying on hoisting from a root package.json. The latter can create "phantom dependencies" – code that compiles because a package is hoisted but isn’t actually listed as a dependency, leading to broken builds when hoisting changes.
Thesis: pnpm catalogs give a centralized, version‑safe way to declare dependency versions
Starting with pnpm 8.6, the catalog: field in pnpm-workspace.yaml lets you define a set of named version entries once and reference them from any workspace manifest. Catalog entries are resolved at install time, respect pnpm’s strict node_modules layout, and fail fast if a reference is missing or invalid.
How catalogs work
- Define a catalog under the
catalog:key inpnpm-workspace.yaml. - Each entry maps a name to a version, range, or workspace protocol (e.g.,
"react": "catalog:react18"). - In a workspace
package.jsonyou use the specialcatalog:protocol:"react": "catalog:react18". - When
pnpm installruns, pnpm looks up the catalog entry, resolves the version, and places the correct package in the isolatednode_modulestree.
Worked example: adding a React catalog to a monorepo
Assume a repo with the following structure:
my-monorepo/ ├── pnpm-workspace.yaml ├── packages/ │ ├── ui/ │ │ └── package.json │ └── api/ │ └── package.json └── ...
- Define the catalog. Edit
pnpm-workspace.yamlat the repo root:
# pnpm-workspace.yaml
packages:
- 'packages/*'
catalog:
react18: "18.2.0"
lodash: "^4.17.21"
- Reference the catalog in a workspace package. Update
packages/ui/package.json:
{
"name": "@myorg/ui",
"dependencies": {
"react": "catalog:react18",
"lodash": "catalog:lodash"
}
}
- Install and verify. Run from the repo root:
pnpm install --filter @myorg/ui
After the install succeeds, you can check that the correct version was used:
pnpm ls --filter @myorg/ui react
# → @myorg/ui@1.0.0 └── react@18.2.0
Or inspect the dependency graph:
pnpm why react
# shows a path that ends with "catalog:react18" → "18.2.0"
Benefits and trade‑offs
- Centralised version control. Changing
react18to "18.3.0" in the catalog updates every workspace that references it after a singlepnpm install. - Strict node_modules. Because pnpm does not hoist dependencies by default, there are no phantom dependencies; each workspace gets exactly the version it declared.
- Multiple named catalogs. You can maintain parallel version cohorts, e.g.,
catalog:react18andcatalog:react19, allowing gradual migration of subsets of packages.
Limitations to keep in mind:
- Catalogs are pnpm‑only. If part of your monorepo is also worked with npm or Yarn workspaces, those tools will ignore the
catalog:field and you lose the benefit. - IDE type acquisition (e.g., VS Code auto‑import) may not resolve
catalog:specifiers without a pnpm‑aware TypeScript plugin; you may need to install types manually or configure your editor. - Over‑fragmentation. Creating many named catalogs can lead to version drift; start with a single default catalog unless you truly need parallel cohorts.
- Renaming or deleting a catalog entry breaks all packages that reference it –
pnpm installwill error with a clear message listing the affected workspaces.
Verification and migration steps
- Confirm you are on a supported version:
pnpm --versionmust be ≥ 8.6.0. - Add a catalog to
pnpm-workspace.yamlas shown above. - Pick one low‑risk workspace, change its dependencies to use the
catalog:protocol, and runpnpm install --filter <workspace>. - After success, repeat for other workspaces, or run
pnpm installat the root to update everything. - To bump all catalog entries to the latest matching versions, use
pnpm update --catalog. - Check the result with
pnpm why <pkg>or by looking atnode_modules/.pnpmto ensure the resolved version matches the catalog entry.
Actionable closing
If you’re managing a monorepo with pnpm, start by adding a single default catalog for the libraries you share across most packages. Migrate one workspace at a time, verify with pnpm why, and then roll out the change more widely. Keep the catalog definition under version control, treat it like any other configuration, and use pnpm update --catalog for regular version bumps. This approach gives you a single source of truth for dependency versions while preserving pnpm’s reliable, isolated node_modules layout.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.