Pnpm Workspaces
Managing monorepo packages with pnpm workspaces including linking, filtering, and dependency hoisting
You are an expert in pnpm workspaces for managing monorepo package structure, dependency resolution, and cross-package linking. ## Key Points - 'packages/*' - 'tools/*' - '!**/test-fixtures/**' - `workspace:*` — Any version; resolves to the current version on publish. - `workspace:^` — Resolves to `^x.y.z` on publish. - `workspace:~` — Resolves to `~x.y.z` on publish. - 'packages/*' 1. **Always use `workspace:` protocol** — Ensures internal packages link correctly and resolve properly on publish. 2. **Set `packageManager` field** — Use Corepack to enforce the exact pnpm version across the team. 3. **Keep `shamefully-hoist=false`** — Default strict mode catches missing dependencies that would break in production. 4. **Use `public-hoist-pattern` selectively** — Only hoist packages that require it (like ESLint plugins) instead of using `shamefully-hoist`. 5. **Use catalogs for shared versions** — Centralize common dependency versions to avoid drift across packages.
skilldb get monorepo-skills/Pnpm WorkspacesFull skill: 224 linespnpm Workspaces — Monorepo Management
You are an expert in pnpm workspaces for managing monorepo package structure, dependency resolution, and cross-package linking.
Core Philosophy
Overview
pnpm workspaces provide native monorepo support through a content-addressable store, strict dependency isolation, and workspace protocol linking. pnpm's approach is faster and more disk-efficient than npm or Yarn, and its strictness catches phantom dependency issues that other package managers miss.
Setup & Configuration
Workspace Definition
Create pnpm-workspace.yaml at the repo root:
packages:
- 'apps/*'
- 'packages/*'
- 'tools/*'
# Exclude test fixtures
- '!**/test-fixtures/**'
Root package.json
{
"name": "my-monorepo",
"private": true,
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"lint": "turbo run lint",
"clean": "pnpm -r exec rm -rf dist node_modules"
},
"devDependencies": {
"turbo": "^2.0.0"
},
"engines": {
"node": ">=18",
"pnpm": ">=9"
},
"packageManager": "pnpm@9.1.0"
}
.npmrc Configuration
# Hoist specific packages needed by tools that don't support strict mode
public-hoist-pattern[]=*eslint*
public-hoist-pattern[]=*prettier*
# Prevent phantom dependencies
shamefully-hoist=false
# Strict peer dependencies
strict-peer-dependencies=true
# Ensure lockfile is up to date in CI
frozen-lockfile=true
Core Patterns
Workspace Protocol
Reference internal packages with the workspace: protocol:
{
"name": "@myorg/web",
"dependencies": {
"@myorg/ui": "workspace:*",
"@myorg/utils": "workspace:^",
"@myorg/config": "workspace:~"
}
}
workspace:*— Any version; resolves to the current version on publish.workspace:^— Resolves to^x.y.zon publish.workspace:~— Resolves to~x.y.zon publish.
Filtering Commands
# Run in a specific package
pnpm --filter @myorg/web dev
# Run in a package and all its dependencies
pnpm --filter @myorg/web... build
# Run in dependents of a package (reverse dependencies)
pnpm --filter ...@myorg/ui build
# Run in packages changed since main
pnpm --filter "...[main]" test
# Run in all packages under apps/
pnpm --filter "./apps/**" lint
# Exclude a package
pnpm --filter "!@myorg/docs" build
Adding Dependencies
# Add to a specific workspace package
pnpm --filter @myorg/web add react
# Add a dev dependency to root
pnpm add -D typescript -w
# Add an internal package as dependency
pnpm --filter @myorg/web add @myorg/ui --workspace
# Add to multiple packages
pnpm --filter "@myorg/web" --filter "@myorg/api" add zod
Recursive Commands
# Run a script in all packages that have it
pnpm -r run build
# Run in all packages, topological order
pnpm -r --sort run build
# Run with concurrency limit
pnpm -r --workspace-concurrency=4 run test
# Execute a command in all packages
pnpm -r exec -- rm -rf dist
Catalogs (pnpm 9+)
Centralize dependency versions across the monorepo in pnpm-workspace.yaml:
packages:
- 'apps/*'
- 'packages/*'
catalog:
react: ^18.3.0
react-dom: ^18.3.0
typescript: ^5.5.0
vitest: ^2.0.0
Then in any package:
{
"dependencies": {
"react": "catalog:",
"react-dom": "catalog:"
}
}
Publishing Configuration
{
"name": "@myorg/ui",
"version": "1.2.0",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"files": ["dist"],
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
}
}
Best Practices
- Always use
workspace:protocol — Ensures internal packages link correctly and resolve properly on publish. - Set
packageManagerfield — Use Corepack to enforce the exact pnpm version across the team. - Keep
shamefully-hoist=false— Default strict mode catches missing dependencies that would break in production. - Use
public-hoist-patternselectively — Only hoist packages that require it (like ESLint plugins) instead of usingshamefully-hoist. - Use catalogs for shared versions — Centralize common dependency versions to avoid drift across packages.
- Use
frozen-lockfilein CI — Prevents accidental lockfile changes during CI runs. - Scope packages with
@org/— Namespaced packages avoid conflicts and make internal vs. external deps clear.
Common Pitfalls
- Phantom dependencies — pnpm's strict isolation means you must declare every dependency. Code that works with npm/Yarn may fail with pnpm because undeclared transitive deps are not accessible.
- Peer dependency warnings — pnpm is strict about peers by default. Use
pnpm config set auto-install-peers trueor resolve them explicitly. - Forgetting
--workspaceflag — Runningpnpm add @myorg/uiwithout--workspacefetches from the registry instead of linking locally. - Not using
-wfor root deps —pnpm add typescriptfails at the root; you must pass-w(workspace root) explicitly. - Lockfile conflicts —
pnpm-lock.yamlconflicts are common in monorepos. Usepnpm install --no-frozen-lockfileto regenerate, then commit the resolved lockfile.
Anti-Patterns
Over-engineering for hypothetical scale. Building for millions of users when you have hundreds adds complexity without value. Solve today's problems first.
Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide well wastes time and introduces unnecessary risk.
Premature abstraction. Creating elaborate frameworks and utilities before you have enough concrete cases to know what the abstraction should look like produces the wrong abstraction.
Neglecting error handling at boundaries. Internal code can trust its inputs, but system boundaries (user input, APIs, file I/O) require defensive validation.
Skipping documentation for obvious code. What is obvious to you today will not be obvious to your colleague next month or to you next year.
Install this skill directly: skilldb add monorepo-skills
Related Skills
Changesets
Versioning and changelog management with Changesets for coordinated monorepo package releases
CI Optimization
CI/CD optimization for monorepos including affected detection, caching strategies, and parallel execution
Dependency Management
Managing internal package dependencies, versioning strategies, and dependency graph health in monorepos
Lerna
Multi-package repository management with Lerna for versioning, publishing, and task orchestration
Nx
Monorepo development with Nx including project graph, generators, executors, and computation caching
Shared Configs
Sharing ESLint, TypeScript, Prettier, and other tool configurations across monorepo packages