Monorepos and workspaces

Maintain reproducible monorepos with Bun Workspaces, Catalogs, isolated installs, and filters

Last updated on

Last verified: 2026-08-02 with Bun 1.3.14.

Decide whether you need an orchestrator

Repository shapeStarting point
A few packages with simple scriptsBun Workspaces only
Medium web monorepo that needs cachingBun Workspaces + Turborepo
Enterprise boundaries, generators, and project graphsEvaluate Nx
TypeScript plus Rust, Go, or other languagesEvaluate moon or the organization's existing build system

Bun owns dependencies, workspaces, and script execution. A task graph, remote cache, or polyglot toolchain supplied by Turborepo, Nx, or moon is a separate responsibility.

Root configuration

package.json
{
  "name": "acme",
  "private": true,
  "packageManager": "bun@1.3.14",
  "workspaces": {
    "packages": ["apps/*", "packages/*"],
    "catalog": {
      "typescript": "6.0.3",
      "zod": "4.1.8"
    }
  },
  "scripts": {
    "typecheck": "bun run --filter '*' typecheck",
    "test": "bun run --filter '*' test",
    "build": "bun run --filter '*' build"
  }
}

Third-party versions above are the page's verification snapshot, not permanent latest recommendations. A Catalog update affects every referencing workspace and should be reviewed as a repository-wide dependency change.

Reference the Catalog and an internal package from a child manifest:

apps/api/package.json
{
  "name": "@acme/api",
  "private": true,
  "dependencies": {
    "@acme/contracts": "workspace:*",
    "zod": "catalog:"
  }
}

Bun resolves workspace: and catalog: to normal versions when publishing public packages. Inspect the final manifest and tarball before release.

Prefer isolated installs for new monorepos

bunfig.toml
[install]
linker = "isolated"

The isolated linker lets packages access only declared dependencies, exposing phantom dependencies earlier. If an older generator or native package requires a flat node_modules, record the failure and deliberately move that repository to hoisted; do not alternate modes unpredictably.

Filter installs and scripts

bun install --filter './apps/api'
bun run --filter '@acme/*' typecheck
bun run --filter '*' test
bun ci

Filters reduce local work, but merge gates still need to verify every affected workspace. Use a task orchestrator when tasks depend on one another or require remote caching rather than relying on directory order.

apps/
  web/             # UI and framework boundary
  api/             # HTTP and MCP entry points
  worker/          # asynchronous work
packages/
  core/            # pure business rules; no direct Bun API dependency
  contracts/       # schemas, DTOs, RPC/OpenAPI types
  database/        # data access and migrations
  config/          # validated configuration loading
  observability/   # logs, traces, and metrics

Keep core as portable TypeScript. Place Bun.SQL, Redis, S3, and Bun.serve in application or adapter layers. This retains Bun-native capabilities while keeping business rules easy to unit test.

CI and release gates

  1. Install the exact Bun version the team has validated.
  2. Run bun ci against the root manifest and bun.lock.
  3. Check that cross-workspace imports are declared.
  4. Run affected tasks, then the full graph on the main branch.
  5. Inspect published manifests after workspace: and catalog: conversion.
  6. Install and test native dependencies on the target OS and CPU architecture.

Keep one dependency source of truth

Do not maintain Bun, npm, pnpm, or Yarn lockfiles indefinitely in one repository. Multiple runtimes may coexist, but dependency resolution and publishing must have one explicit owner.

Official references: Workspaces, Catalogs, and bun install.