Title: Using pnpm Catalogs in monorepos
Slug: using-pnpm-catalogs-in-monorepos
Date: 2025-12-14 13:15:00
Author: Kartones
Lang: en
Tags: Development, Javascript, Typescript, Tools, Monorepos
og_image:
Description: An overview of using pnpm catalogs to centralize dependency versions in a monorepo, with a practical example and a brief note on handling Bazel-specific integration issues.


If you work with JavaScript or TypeScript, [pnpm](https://pnpm.io/) is a strong choice due to its efficient package management without any configuration step. What really makes this tool stand out is the myriad of extra features. Case in point: At work, we're consolidating several large web repositories into a single monorepo. Some of these repositories are already monorepos themselves, which results in many `package.json` files with overlapping dependencies and differing versions.

Simplifying that maintenance typically requires some form of centralized dependency management, often with some kind of [software bill of materials (SBOM)](https://www.ibm.com/think/topics/sbom). pnpm supports this via its [Catalogs feature](https://pnpm.io/catalogs). The feature is so flexible that it allows you to support either a single-version policy, or multiple versions of dependencies.

Rather than duplicating pnpm's documentation, I'll show how I enabled this feature in a small personal project.

First, you need to create a catalogue section in your workspace file, `pnpm-workspace.yaml` ([source](https://github.com/Kartones/bazel-web-template/blob/a34d2290bff91ff622b2583e425f0212e9f73fce/pnpm-workspace.yaml#L4-L8)):

```yaml
catalog:
  "@types/chai": 5.2.3
  "@types/node": 24.10.1
  chai: 6.2.1
  typescript: 5.9.3
```

And then, you simply replace your `dependencies`, `devDependencies` and related, in your `package.json` files, like this ([source](https://github.com/Kartones/bazel-web-template/blob/a34d2290bff91ff622b2583e425f0212e9f73fce/package.json#L15-L18)):

From:
```json
  "devDependencies": {
    "@types/chai": "5.2.3",
    "@types/node": "24.10.1",
    "chai": "6.2.1",
    "typescript": "5.9.3"
  },
```

To:
```json
  "devDependencies": {
    "@types/chai": "catalog:",
    "@types/node": "catalog:",
    "chai": "catalog:",
    "typescript": "catalog:"
  },
```


At this point, running `pnpm install` completes the setup. However, additional configuration is required when using Bazel. If you run this through Bazel, you will face a `rules_ts` error trying to resolve `typescript`'s version as `catalog:`. Thankfully, the fix is simple, as `rules_js` exposes a way to query the resolved version of any Bazel-managed npm dependency.

In your `MODULE.bazel`, when you configure the `rules_ts` extension to tell it where to pick TypeScript's version from, change the following ([source](https://github.com/Kartones/bazel-web-template/blob/a34d2290bff91ff622b2583e425f0212e9f73fce/MODULE.bazel#L49)):

From:
```python
rules_ts_ext.deps(
    ts_version_from = "//:package.json",
)
```

To:
```python
rules_ts_ext.deps(
    ts_version_from = "@npm//:typescript/resolved.json",
)
```

pnpm's lockfile, `pnpm-lock.yaml`, already contains the resolved versions of all dependencies, so `rules_js` fully supports the pnpm catalogs feature. When you face tooling that relies on analyzing `package.json`, like `rules_ts`'s special treatment of the `typescript` dependency, you need to resort to alternatives. The solution is straightforward and avoids compromising the benefits of using pnpm catalogs.
