# Workspaces

> Develop complex monorepos with multiple independent packages

Bun supports [`workspaces`](https://docs.npmjs.com/cli/v9/using-npm/workspaces?v=true#description) in `package.json`. With workspaces, you develop several independent packages in a single repository, a _monorepo_.

A monorepo commonly has this structure:

```txt File Tree icon="folder-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`.

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

<Note>
  **Glob support** — Bun supports full glob syntax in `"workspaces"`, including negative patterns such as
  `!**/excluded/**`. See [supported glob patterns](/runtime/glob#supported-glob-patterns).
</Note>

```json package.json icon="file-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`.

```json packages/pkg-a/package.json icon="file-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.

```bash
# 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](/pm/filter) to run `package.json` scripts in several packages at once, or `--workspaces` to 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):

```json title="apps/desktop/package.json" icon="file-json"
{
  "name": "desktop",
  "installConfig": { "hoistingLimits": "workspaces" }
}
```

or from the root `package.json`, next to the workspace globs:

```json title="package.json" icon="file-json"
{
  "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](/pm/catalogs).

<Note>
⚡️ **Speed** — Installs are fast, even for big monorepos. Bun installs the [Remix](https://github.com/remix-run/remix) monorepo in about `500ms` on Linux.

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

<Image src="https://user-images.githubusercontent.com/709451/212829600-77df9544-7c9f-4d8d-a984-b2cd0fd2aa52.png" />
</Note>
