Workspaces

Develop complex monorepos with multiple independent packages

Bun supports workspaces in package.json. With workspaces, you develop several independent packages in a single repository, a monorepo.

A monorepo commonly has this structure:

File Tree
<root>
├── README.md
├── bun.lock
├── package.json
├── tsconfig.json
└── packages
    ├── pkg-a
    │   ├── index.ts
    │   ├── package.json
    │   └── tsconfig.json
    ├── pkg-b
    │   ├── index.ts
    │   ├── package.json
    │   └── tsconfig.json
    └── pkg-c
        ├── index.ts
        ├── package.json
        └── tsconfig.json

The "workspaces" key in the root package.json lists the subdirectories to treat as workspaces. By convention, they live in a directory called packages.

package.json
{
  "name": "my-project",
  "version": "1.0.0",
  "workspaces": ["packages/*"],
  "devDependencies": {
    "example-package-in-monorepo": "workspace:*"
  }
}

Glob support — Bun supports full glob syntax in "workspaces", including negative patterns such as !**/excluded/**. See supported glob patterns.

package.json
{
  "name": "my-project",
  "version": "1.0.0",
  "workspaces": ["packages/**", "!packages/**/test/**", "!packages/**/template/**"]
}

Each workspace has its own package.json. To reference another package in the monorepo, use a semver range or the workspace protocol (for example workspace:*) as the version in your package.json.

packages/pkg-a/package.json
{
  "name": "pkg-a",
  "version": "1.0.0",
  "dependencies": {
    "pkg-b": "workspace:*"
  }
}

bun install installs dependencies for all workspaces in the monorepo, de-duplicating packages if possible. To install dependencies for specific workspaces only, use the --filter flag.

# Install dependencies for all workspaces starting with `pkg-` except for `pkg-c`
bun install --filter "pkg-*" --filter "!pkg-c"

# Paths can also be used. This is equivalent to the command above.
bun install --filter "./packages/pkg-*" --filter "!pkg-c" # or --filter "!./packages/pkg-c"

When publishing, Bun replaces workspace: versions with the package's package.json version:

"workspace:*" -> "1.0.1"
"workspace:^" -> "^1.0.1"
"workspace:~" -> "~1.0.1"

A specific version takes precedence over the package's package.json version:

"workspace:1.0.2" -> "1.0.2" // Even if current version is 1.0.1

Workspaces have a few major benefits.

  • Split code into logical parts. If one package relies on another, add it as a dependency in package.json. If package b depends on a, bun install installs your local packages/a directory into node_modules instead of downloading it from the npm registry.
  • Bun can de-duplicate dependencies. If a and b share a common dependency, Bun hoists it to the root node_modules directory. This saves disk space and minimizes the "dependency hell" of multiple versions of a package installed at once.
  • Run scripts in multiple packages. Use the --filter flag to run package.json scripts in several packages at once, or --workspaces to run scripts across all workspaces.

Share versions with Catalogs#

When many packages need the same dependency versions, define those versions once in a catalog in the root package.json and reference them from your workspaces with the catalog: protocol. Updating the catalog updates every package that references it. See Catalogs.

⚡️ Speed — Installs are fast, even for big monorepos. Bun installs the Remix monorepo in about 500ms on Linux.

  • 28x faster than npm install
  • 12x faster than yarn install (v1)
  • 8x faster than pnpm install