Catalogs

Share common dependency versions across multiple packages in a monorepo

Catalogs share dependency versions across the packages in a monorepo. Rather than repeating the same versions in each workspace package, you define them once in the root package.json and reference them throughout your project.

Overview#

Instead of each workspace package specifying its own versions, you:

  1. Define version catalogs in the root package.json
  2. Reference those versions with the catalog: protocol
  3. Update every package at once by changing the version in one place

Catalogs matter most in large monorepos where dozens of packages depend on the same versions of key dependencies.

How to Use Catalogs#

Directory Structure Example#

Consider a monorepo with the following structure:

my-monorepo/
├── package.json
├── bun.lock
└── packages/
    ├── app/
    │   └── package.json
    ├── ui/
    │   └── package.json
    └── utils/
        └── package.json

1. Define Catalogs in Root package.json#

In your root-level package.json, add a catalog or catalogs field within the workspaces object:

package.json
{
  "name": "my-monorepo",
  "workspaces": {
    "packages": ["packages/*"],
    "catalog": {
      "react": "^19.0.0",
      "react-dom": "^19.0.0"
    },
    "catalogs": {
      "testing": {
        "jest": "30.0.0",
        "testing-library": "14.0.0"
      }
    }
  }
}

catalog and catalogs also work at the top level of package.json.

2. Reference Catalog Versions in Workspace Packages#

In your workspace packages, use the catalog: protocol to reference versions:

packages/app/package.json
{
  "name": "app",
  "dependencies": {
    "react": "catalog:",
    "react-dom": "catalog:",
    "jest": "catalog:testing"
  }
}
packages/ui/package.json
{
  "name": "ui",
  "dependencies": {
    "react": "catalog:",
    "react-dom": "catalog:"
  },
  "devDependencies": {
    "jest": "catalog:testing",
    "testing-library": "catalog:testing"
  },
  "peerDependencies": {
    "react": "catalog:"
  }
}

catalog: references work in dependencies, devDependencies, optionalDependencies, peerDependencies, and as the value of a root overrides rule. A catalog reference behaves exactly as if the catalog's range were written inline.

3. Run Bun Install#

Run bun install to install all dependencies according to the catalog versions.

Catalog vs Catalogs#

Bun supports two ways to define catalogs:

  1. catalog (singular): A single default catalog for commonly used dependencies

    package.json
    "catalog": {
      "react": "^19.0.0",
      "react-dom": "^19.0.0"
    }

    Reference with catalog::

    packages/app/package.json
    "dependencies": {
      "react": "catalog:"
    }
  2. catalogs (plural): Multiple named catalogs for grouping dependencies

    package.json
    "catalogs": {
      "testing": {
        "jest": "30.0.0"
      },
      "ui": {
        "tailwind": "4.0.0"
      }
    }

    Reference with catalog:<name>:

    packages/app/package.json
    "dependencies": {
      "jest": "catalog:testing",
      "tailwind": "catalog:ui"
    }

Benefits of Using Catalogs#

  • Consistency: All packages use the same version of critical dependencies
  • Maintenance: Update a dependency version in one place instead of across multiple package.json files
  • Clarity: Makes it obvious which dependencies are standardized across your monorepo
  • Simplicity: No extra version resolution strategies or external tools

Real-World Example#

A larger example, for a React application:

Root package.json

package.json
{
  "name": "react-monorepo",
  "workspaces": {
    "packages": ["packages/*"],
    "catalog": {
      "react": "^19.0.0",
      "react-dom": "^19.0.0",
      "react-router-dom": "^6.15.0"
    },
    "catalogs": {
      "build": {
        "webpack": "5.88.2",
        "babel": "7.22.10"
      },
      "testing": {
        "jest": "29.6.2",
        "react-testing-library": "14.0.0"
      }
    }
  },
  "devDependencies": {
    "typescript": "5.1.6"
  }
}
packages/app/package.json
{
  "name": "app",
  "dependencies": {
    "react": "catalog:",
    "react-dom": "catalog:",
    "react-router-dom": "catalog:",
    "@monorepo/ui": "workspace:*",
    "@monorepo/utils": "workspace:*"
  },
  "devDependencies": {
    "webpack": "catalog:build",
    "babel": "catalog:build",
    "jest": "catalog:testing",
    "react-testing-library": "catalog:testing"
  }
}
packages/ui/package.json
{
  "name": "@monorepo/ui",
  "dependencies": {
    "react": "catalog:",
    "react-dom": "catalog:"
  },
  "devDependencies": {
    "jest": "catalog:testing",
    "react-testing-library": "catalog:testing"
  }
}
packages/utils/package.json
{
  "name": "@monorepo/utils",
  "dependencies": {
    "react": "catalog:"
  },
  "devDependencies": {
    "jest": "catalog:testing"
  }
}

Updating Versions#

To update versions across all packages, change the version in the root package.json:

package.json
"catalog": {
  "react": "^19.1.0",  // Updated from ^19.0.0
  "react-dom": "^19.1.0"  // Updated from ^19.0.0
}

Then run bun install to update all packages.

Adding to the catalog with bun add#

bun add --catalog adds the entry to the root catalog and writes "catalog:" to the current package; --catalog=<name> adds it to catalogs.<name> and writes "catalog:<name>". Bun reuses an existing catalog entry unless you pass an explicit version. See bun add --catalog.

terminal
bun add react --catalog

Even without the flag, bun add react (no version) writes "catalog:" when the default catalog already lists react. Pass a version to write a concrete range instead.

Lockfile Integration#

Bun's lockfile tracks catalog versions, so installs are consistent across environments. The lockfile includes:

  • The catalog definitions from your package.json
  • The resolution of each cataloged dependency
bun.lock(excerpt)
{
  "lockfileVersion": 2,
  "workspaces": {
    "": {
      "name": "react-monorepo",
    },
    "packages/app": {
      "name": "app",
      "dependencies": {
        "react": "catalog:",
        "react-dom": "catalog:",
        ...
      },
    },
    ...
  },
  "catalog": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    ...
  },
  "catalogs": {
    "build": {
      "webpack": "5.88.2",
      ...
    },
    ...
  },
  "packages": {
    ...
  }
}

Limitations and Edge Cases#

  • Catalog references must match a dependency defined in either catalog or one of the named catalogs
  • Bun ignores empty strings and whitespace in catalog names and treats them as the default catalog
  • catalog:default is the same as catalog:. You can define the default catalog as either catalog or catalogs.default, but a package listed in both is an error
  • Invalid dependency versions in catalogs fail to resolve during bun install
  • catalog: only works in the root and workspace package.json files. Inside a published package it fails to resolve. Publish with bun publish or bun pm pack, which replace it with the real range (see Publishing)

Publishing#

When you run bun publish or bun pm pack, Bun replaces catalog: references in your package.json with the resolved version numbers. The published package includes regular semver strings and no longer depends on your catalog definitions.