pnpm Workspaces for Monorepo Management
Quick Guide: pnpm 10.x workspaces for monorepo management.
pnpm-workspace.yamldefines workspace packages.workspace:*protocol for internal linking.catalog:protocol for dependency version synchronization.--filterfor targeted commands. Sharedtsconfigand tooling config across packages. Changesets for versioning and publishing. Strict dependency isolation by default.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST use workspace:* protocol for ALL internal package dependencies -- never hardcode versions)
(You MUST use --frozen-lockfile in CI -- pnpm enables this by default in CI environments)
(You MUST define workspace packages in pnpm-workspace.yaml at the repository root)
(You MUST put pnpm-specific settings in pnpm-workspace.yaml -- NOT .npmrc (pnpm v10 change))
(You MUST use catalog: protocol when sharing dependency versions across 3+ packages)
(You MUST use --filter for targeted commands instead of pnpm -r when only specific packages changed)
</critical_requirements>
Auto-detection: pnpm-workspace.yaml, pnpm workspaces, workspace protocol, workspace:*, catalog:, pnpm filter, pnpm recursive, pnpm monorepo, pnpm-lock.yaml, .npmrc pnpm, pnpm catalogs, pnpm publish
When to use:
- Setting up a monorepo with pnpm workspaces
- Configuring
pnpm-workspace.yamlfor workspace package discovery - Linking internal packages with
workspace:*protocol - Synchronizing dependency versions with
catalog:protocol - Running scripts across workspaces with
--filteror-r - Publishing packages from a pnpm workspace
- Setting up CI/CD pipelines with pnpm caching
- Sharing TypeScript, ESLint, or Prettier config across workspace packages
- Migrating from npm/yarn workspaces to pnpm
When NOT to use:
- Single-package projects with no shared code
- Projects using Bun or Yarn as their package manager
- Projects that need npm compatibility exclusively (e.g., npm workspaces)
- Task orchestration logic (use a dedicated task runner on top of pnpm)
Key patterns covered:
pnpm-workspace.yamlsetup and workspace package discovery- Workspace protocol (
workspace:*,workspace:^,workspace:~) - Catalogs for dependency version synchronization
- Filtering commands (
--filter,-F, glob patterns, dependency selectors) - Running scripts across workspaces (
-r,--parallel,--workspace-concurrency) - Settings in
pnpm-workspace.yaml(v10: settings moved from.npmrc) - Publishing with
publishConfigand changesets - CI/CD with GitHub Actions, caching, and
--frozen-lockfile - Shared TypeScript configuration patterns
- Monorepo directory structure conventions
Detailed resources:
- Core Setup -- pnpm-workspace.yaml, .npmrc, directory structure, settings
- Shared Packages -- workspace protocol, catalogs, TypeScript/ESLint config
- Scripts & Filtering -- --filter, recursive execution, dependency management
- Publishing & Versioning -- changesets, publishConfig, Docker
- CI/CD Pipelines -- GitHub Actions, automated release
- Quick Command Reference -- condensed lookup table
<philosophy>
Philosophy
pnpm workspaces provide strict, efficient monorepo management with content-addressable storage. Unlike npm/yarn, pnpm creates a non-flat node_modules where packages can only access their declared dependencies -- this strictness catches missing dependency declarations early. The workspace protocol (workspace:*) ensures internal packages always link locally, while catalogs (catalog:) centralize version management to eliminate version drift.
Core principles:
- Strict by default -- packages cannot access undeclared dependencies
- Disk efficient -- content-addressable store shares identical files across projects
- Security first -- v10 blocks lifecycle scripts by default to prevent supply chain attacks
- Single lockfile -- one
pnpm-lock.yamlat the workspace root for all packages
When to use pnpm workspaces:
- Monorepos with multiple apps and shared packages
- Projects that need strict dependency isolation
- Teams that want disk-efficient dependency storage
- Publishing multiple related npm packages from one repo
When NOT to use:
- Single-package projects (no workspace benefits)
- Projects deeply invested in Yarn PnP or Berry features
- Environments where only npm is available (some CI/CD, restricted corporate setups)
<patterns>
Core Patterns
Pattern 1: Workspace Configuration (pnpm-workspace.yaml)
The pnpm-workspace.yaml file at the repository root defines which directories contain workspace packages. Every pnpm workspace MUST have this file.
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
In pnpm v10, settings moved from .npmrc to this file:
# pnpm-workspace.yaml (v10+)
packages:
- "apps/*"
- "packages/*"
linkWorkspacePackages: true
saveWorkspaceProtocol: rolling
disallowWorkspaceCycles: true
Why good: Single source of truth for workspace definition and settings
See examples/core.md for full workspace setup, directory structure, and all settings.
Pattern 2: Workspace Protocol (workspace:*)
The workspace protocol ensures internal packages always resolve to the local workspace version, never from the registry.
| Protocol | During Development | After pnpm publish |
|---|---|---|
workspace:* | Links to local package | Replaced with exact version (e.g., 1.5.0) |
workspace:^ | Links to local package | Replaced with caret range (e.g., ^1.5.0) |
workspace:~ | Links to local package | Replaced with tilde range (e.g., ~1.5.0) |
{
"dependencies": {
"@repo/ui": "workspace:*",
"@repo/types": "workspace:*"
}
}
Why good: Guarantees local linking, pnpm refuses to resolve externally, version conversion on publish ensures correct semver
{
"dependencies": {
"@repo/ui": "^1.0.0"
}
}
Why bad: Hardcoded versions may install from npm registry instead of local workspace, version mismatches across packages
See examples/packages.md for protocol variants, aliasing, and internal package setup.
Pattern 3: Catalogs for Version Synchronization
Catalogs define dependency versions once in pnpm-workspace.yaml and reference them across all packages with catalog:.
# pnpm-workspace.yaml
catalog:
react: ^19.0.0
react-dom: ^19.0.0
typescript: ^5.7.0
{
"dependencies": {
"react": "catalog:",
"react-dom": "catalog:"
}
}
Why good: Single version source of truth, updating one line updates all packages, eliminates merge conflicts
Named catalogs support version migration:
catalogs:
react18:
react: ^18.3.1
react19:
react: ^19.0.0
See examples/packages.md for named catalogs, strict enforcement, and full examples.
Pattern 4: Filtering Commands
--filter (or -F) restricts commands to specific packages instead of running across the entire workspace.
# Exact package
pnpm --filter @repo/web-app build
# Package and ALL its dependencies
pnpm --filter "web-app..." build
# Changed packages since main
pnpm --filter "...[origin/main]" test
# Exclude a package
pnpm --filter "!@repo/docs" build
Why good: Targeted execution saves CI time, dependency-aware filtering ensures correct build order
# BAD: Running everything when only one package changed
pnpm -r build
**Wh