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:
<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.jsonThe "workspaces" key in the root package.json lists the subdirectories to treat as workspaces. By convention, they live in a directory called packages.
{
"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.
{
"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.
{
"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.1Workspaces 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 packagebdepends ona,bun installinstalls your localpackages/adirectory intonode_modulesinstead of downloading it from the npm registry. - Bun can de-duplicate dependencies. If
aandbshare a common dependency, Bun hoists it to the rootnode_modulesdirectory. 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
--filterflag to runpackage.jsonscripts in several packages at once, or--workspacesto run scripts across all workspaces.
Self-contained workspaces#
With the hoisted linker, dependencies shared by several workspaces are hoisted to the root
node_modules. Some tools cannot follow that: Electron packagers and serverless bundlers walk,
prune and repackage one workspace's node_modules and expect every dependency to be physically
present under it. Mark such a workspace as self-contained, either in its own package.json
(the same key Yarn uses; only the "workspaces" value is recognized):
{
"name": "desktop",
"installConfig": { "hoistingLimits": "workspaces" }
}or from the root package.json, next to the workspace globs:
{
"workspaces": {
"packages": ["apps/*", "packages/*"],
"selfContained": ["apps/desktop"]
}
}(entries are workspace paths or package names)
For that workspace bun install then behaves as a hoisting barrier — nothing it depends on,
directly or transitively (including through other workspaces it depends on), is placed above
apps/desktop/node_modules, so that directory is a complete tree — and materializes those
packages as real copies instead of hardlinks / clones from the cache, so tools that rewrite them
cannot affect the cache or other projects. All other workspaces keep hoisting to the root as usual. The setting is recorded in bun.lock for the workspace, so installs from the lockfile reproduce the same layout.
This setting has no effect with the isolated linker, where every package already resolves only its
own dependencies.
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
