Skip to main content
Bun supports workspaces in package.json, making it straightforward to develop a monorepo composed of several independent packages. A typical monorepo structure looks like this:
<root>
├── bun.lock
├── package.json
├── tsconfig.json
└── packages
    ├── pkg-a
    │   ├── index.ts
    │   └── package.json
    ├── pkg-b
    │   ├── index.ts
    │   └── package.json
    └── pkg-c
        ├── index.ts
        └── package.json

Configuring workspaces

Declare workspaces in the root package.json using glob patterns:
{
  "name": "my-project",
  "version": "1.0.0",
  "workspaces": ["packages/*"]
}
Bun supports full glob syntax in "workspaces", including negation patterns. For example: ["packages/**", "!packages/**/test/**"].

Referencing workspace packages

Each workspace has its own package.json. Use the workspace: protocol to reference other packages in the monorepo:
{
  "name": "pkg-a",
  "version": "1.0.0",
  "dependencies": {
    "pkg-b": "workspace:*"
  }
}
Supported workspace: specifiers:
SpecifierResolved to on publish
workspace:*exact current version, e.g. 1.0.1
workspace:^^1.0.1
workspace:~~1.0.1
workspace:1.0.21.0.2 (overrides package.json version)
When you publish a workspace package, workspace: specifiers are automatically replaced with the resolved version.

Installing dependencies

Running bun install from the root installs dependencies for all workspaces. Shared packages are deduplicated and hoisted to the root node_modules.
bun install
Fast monorepo installs — Bun installs the Remix monorepo in ~500ms on Linux: 28x faster than npm, 12x faster than Yarn v1, 8x faster than pnpm.

Benefits

  • Code splitting: Workspace packages can declare each other as dependencies. bun install symlinks local packages instead of downloading from npm.
  • Deduplication: Shared dependencies are hoisted to the root node_modules, avoiding redundant installations.
  • Script execution: Use --filter to run scripts across multiple packages.

Filtering with --filter

Install dependencies for a subset of workspaces:
# Install for all workspaces matching pkg-* except pkg-c
bun install --filter "pkg-*" --filter "!pkg-c"

# Install only for the package at a specific path
bun install --filter "./packages/pkg-a"
Run scripts across matching workspaces:
# Run the build script in all packages matching pkg-*
bun run --filter "pkg-*" build

# Run tests in all workspaces
bun run --workspaces test

Shared versions with Catalogs

When many workspace packages share the same dependency versions, define those versions once in the root package.json using catalogs and reference them with the catalog: protocol.
{
  "workspaces": {
    "packages": ["packages/*"],
    "catalog": {
      "react": "^18.0.0",
      "typescript": "^5.0.0"
    }
  }
}
Then in a workspace package.json:
{
  "dependencies": {
    "react": "catalog:"
  },
  "devDependencies": {
    "typescript": "catalog:"
  }
}
Updating the catalog version in the root automatically updates every package that references it.

Build docs developers (and LLMs) love