Skip to main content
Workspaces allow you to manage multiple packages within a single repository (monorepo).

Configuration

Define workspaces in the root package.json:
{
  "workspaces": ["packages/*"]
}
This tells Bun that every directory in packages/ is a workspace.

Glob patterns

Use glob patterns to match workspace directories:
{
  "workspaces": [
    "packages/*",
    "apps/*",
    "tools/*"
  ]
}

Negative patterns

Exclude specific directories:
{
  "workspaces": [
    "packages/*",
    "!packages/ignored"
  ]
}

Structure

Typical monorepo structure:
my-monorepo/
├── package.json          # Root package.json with workspaces
├── bun.lockb             # Shared lockfile
├── node_modules/         # Shared dependencies
├── packages/
│   ├── app/
│   │   ├── package.json
│   │   └── src/
│   ├── shared/
│   │   ├── package.json
│   │   └── src/
│   └── utils/
│       ├── package.json
│       └── src/
└── apps/
    └── web/
        ├── package.json
        └── src/

Installing

Install all workspaces

From the root:
bun install
Installs dependencies for all workspace packages and links inter-workspace dependencies.

Install in specific workspace

cd packages/app
bun install
Or from root:
bun install --cwd packages/app

Inter-workspace dependencies

Reference by name

Workspace packages can depend on each other:
// packages/app/package.json
{
  "name": "@myorg/app",
  "dependencies": {
    "@myorg/shared": "workspace:*"
  }
}
The workspace: protocol tells Bun to link to the local workspace package.

Version protocols

{
  "dependencies": {
    "@myorg/shared": "workspace:*",      // Any version
    "@myorg/utils": "workspace:^1.0.0",  // Semver range
    "@myorg/types": "workspace:~",       // Current version
  }
}

Automatic linking

When you run bun install, workspace dependencies are automatically linked:
$ bun install
bun install v1.0.0

+ @myorg/shared (workspace)
+ @myorg/utils (workspace)

134 packages installed [1.83s]

Adding dependencies

Add to specific workspace

From workspace directory:
cd packages/app
bun add react
From root:
bun add react --cwd packages/app

Add workspace dependency

cd packages/app
bun add @myorg/shared@workspace:*
Or edit package.json manually:
{
  "dependencies": {
    "@myorg/shared": "workspace:*"
  }
}
Then run bun install.

Add to root workspace

Add shared development dependencies to root:
# From root
bun add -D typescript @types/node
Root package.json:
{
  "workspaces": ["packages/*"],
  "devDependencies": {
    "typescript": "^5.0.0",
    "@types/node": "^20.0.0"
  }
}

Running scripts

Run in specific workspace

cd packages/app
bun run build
Or from root:
bun --cwd packages/app run build

Run in all workspaces

Run a script across all workspaces:
# Using a script runner like Turborepo
turbo run build

# Or manually
for dir in packages/*; do
  (cd "$dir" && bun run build)
done

Filtering workspaces

Run commands in specific workspaces:
# Install only for specific workspace
bun install --filter "@myorg/app"

# Update packages in specific workspace
bun update --filter "@myorg/app"

# Check outdated in specific workspace
bun outdated --filter "@myorg/app"
With patterns:
# All packages with @myorg scope
bun install --filter "@myorg/*"

# Multiple filters
bun install --filter "@myorg/app" --filter "@myorg/shared"

Workspace resolution

Bun resolves workspace dependencies in this order:
  1. Exact workspace match
  2. Version range match within workspaces
  3. External registry

Resolution examples

Given workspaces:
  • @myorg/utils version 1.0.0
  • @myorg/helpers version 2.0.0
{
  "dependencies": {
    "@myorg/utils": "workspace:*",      // → @myorg/[email protected] (workspace)
    "@myorg/utils": "workspace:^1.0.0", // → @myorg/[email protected] (workspace)
    "@myorg/utils": "workspace:^2.0.0", // → @myorg/utils@latest (registry)
    "@myorg/utils": "*"                 // → @myorg/[email protected] (workspace)
  }
}

Publishing workspaces

Publish individual workspace

cd packages/utils
bun publish

Workspace protocol in published packages

When publishing, workspace: dependencies are converted: Before publishing:
{
  "dependencies": {
    "@myorg/shared": "workspace:^1.0.0"
  }
}
Published version:
{
  "dependencies": {
    "@myorg/shared": "^1.0.0"
  }
}

Pre-publish validation

Ensure workspace dependencies exist in registry before publishing:
# Publish shared packages first
cd packages/shared && bun publish

# Then publish packages that depend on them
cd ../app && bun publish

Common patterns

Shared TypeScript config

Root tsconfig.json:
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "strict": true
  }
}
Workspace tsconfig.json:
{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src"
  }
}

Shared development tools

Install shared tools at root:
{
  "devDependencies": {
    "typescript": "^5.0.0",
    "@biomejs/biome": "^1.0.0",
    "vitest": "^1.0.0"
  },
  "scripts": {
    "test": "vitest",
    "lint": "biome check .",
    "format": "biome format --write ."
  }
}

Private packages

Mark workspace packages as private if not publishing:
{
  "name": "@myorg/app",
  "private": true
}

Benefits

Code sharing

Share code between packages without publishing:
// packages/shared/src/utils.ts
export function formatDate(date: Date) {
  return date.toISOString();
}

// packages/app/src/index.ts
import { formatDate } from "@myorg/shared";

Dependency deduplication

Shared dependencies are hoisted to root node_modules:
node_modules/
├── react/           # Shared by multiple workspaces
├── react-dom/       # Shared by multiple workspaces
└── typescript/      # Shared dev dependency

Atomic changes

Make changes across multiple packages in a single commit:
# Update API in shared package
cd packages/shared
# Update code

# Update consumers
cd ../app
# Update code to match new API

# Commit everything together
git add .
git commit -m "Update API"

Troubleshooting

Workspace not found

If Bun can’t find a workspace:
  1. Check workspace glob patterns in root package.json
  2. Ensure workspace has a package.json with name field
  3. Run bun install from root

Dependencies not linking

If workspace dependencies aren’t linking:
# Clear cache and reinstall
rm -rf node_modules bun.lockb
bun install

Version mismatches

Ensure workspace versions match dependency ranges:
// packages/shared/package.json
{
  "version": "1.0.0"
}

// packages/app/package.json
{
  "dependencies": {
    "@myorg/shared": "workspace:^1.0.0"  // Matches!
  }
}

Migration from other package managers

From npm/Yarn workspaces

Bun workspaces are compatible:
// Works with Bun
{
  "workspaces": ["packages/*"]
}
Replace workspace: protocols:
// Before (Yarn)
{"dependencies": {"pkg": "workspace:*"}}

// After (Bun, same!)
{"dependencies": {"pkg": "workspace:*"}}

From pnpm

Convert pnpm-workspace.yaml:
# pnpm-workspace.yaml
packages:
  - 'packages/*'
To package.json:
{
  "workspaces": ["packages/*"]
}

Examples

Full stack monorepo

{
  "name": "my-app",
  "workspaces": [
    "packages/*",
    "apps/*"
  ],
  "devDependencies": {
    "typescript": "^5.0.0"
  }
}
Structure:
├── apps/
│   ├── web/           # React frontend
│   └── api/           # Backend API
├── packages/
│   ├── ui/            # Shared UI components
│   ├── utils/         # Shared utilities
│   └── types/         # Shared TypeScript types
└── package.json

Library monorepo

{
  "name": "@mylib/monorepo",
  "workspaces": ["packages/*"],
  "private": true
}
Structure:
├── packages/
│   ├── core/          # @mylib/core
│   ├── react/         # @mylib/react
│   ├── vue/           # @mylib/vue
│   └── utils/         # @mylib/utils
└── package.json

Build docs developers (and LLMs) love