pnpm Reference: Install, Store, Workspaces, Catalog & CI
pnpm is a fast, disk-efficient package manager. The key difference from npm and Yarn: packages are stored once in a global content-addressable store and hard-linked into projects. A 200MB package installed in 50 projects uses 200MB total, not 10GB. It’s also strict about phantom dependencies — your code can’t import a package that isn’t in your package.json, which prevents bugs that npm allows. Drop-in compatible with npm commands. The monorepo workspace support is first-class.
1. Install & Core Commands
Install, add/remove/update, filtering, and common CLI flags
# Install pnpm: npm install -g pnpm # Or: corepack enable && corepack use pnpm@latest # Specify version in package.json (recommended for teams): # "packageManager": "pnpm@9.15.0" # Add packages: pnpm add express # production dependency pnpm add -D typescript # devDependency pnpm add -O nodemon # optionalDependency pnpm add express@4.18.0 # specific version pnpm add github:org/repo # GitHub source # Remove: pnpm remove express pnpm remove -D typescript # Install all dependencies (from lockfile): pnpm install # fast — uses lockfile pnpm install --frozen-lockfile # CI — fail if lockfile outdated # Update packages: pnpm update # update to latest within semver range pnpm update --latest # update to absolute latest (ignores semver) pnpm update express # update specific package pnpm outdated # show which packages have new versions # Run scripts: pnpm run build pnpm build # shorthand (omit "run") pnpm test pnpm dev # Execute binaries (like npx): pnpm dlx create-next-app@latest # equivalent to npx pnpm exec eslint src/ # run local binary
2. Content-Addressable Store & Disk Efficiency
How pnpm’s store works, virtual store, hard links vs symlinks, and store management
# Global store location: # macOS/Linux: ~/.local/share/pnpm/store # Windows: ~/AppData/Local/pnpm/store # How it works: # 1. First install: package files copied to global store (content-addressed by hash) # 2. Subsequent installs: hard links from store to node_modules (no copy) # 3. Two projects using lodash@4.17.21 share ONE copy on disk # Check store location: pnpm store path # Clean up unused packages from store: pnpm store prune # removes unreferenced packages # Verify store integrity: pnpm store verify # Virtual store — node_modules structure: # node_modules/ # .pnpm/ ← virtual store with all actual packages # express@4.18.0/node_modules/express/ # lodash@4.17.21/node_modules/lodash/ # express → .pnpm/express@4.18.0/node_modules/express (symlink) # lodash → (NOT here unless in your package.json) # Phantom dependency protection: # npm: you CAN import lodash even if it's only in express's dependencies # pnpm: you CANNOT — only packages in YOUR package.json are accessible # This catches bugs that npm silently allows # .npmrc for pnpm config: # shamefully-hoist=true # npm-like flat node_modules (avoid if possible) # strict-peer-dependencies=false # disable peer dep errors # auto-install-peers=true # auto-install missing peer deps
3. Workspaces (Monorepo)
pnpm-workspace.yaml, filtering with –filter, and recursive commands
# pnpm-workspace.yaml — define workspace packages:
packages:
- "apps/*"
- "packages/*"
- "tools/*"
# Install all workspace packages (from root):
pnpm install # installs dependencies for all packages
# Run command in specific workspace:
pnpm --filter my-app build
pnpm --filter my-app dev
pnpm --filter "./packages/*" build # all packages matching glob
# Run in all workspaces:
pnpm --recursive run build
pnpm -r run build # shorthand
# Run in parallel:
pnpm --recursive --parallel run test
# Filter by dependency (build everything that depends on @my-org/ui):
pnpm --filter "...@my-org/ui" build # @my-org/ui + all dependents
pnpm --filter "@my-org/ui..." build # @my-org/ui + all its dependencies
# Add dependency to specific workspace:
pnpm add react --filter my-app
# Add workspace package as dependency (local reference):
pnpm add @my-org/ui --filter my-app --workspace
# Adds: "@my-org/ui": "workspace:*" in my-app/package.json
# workspace: protocol — links local packages:
# package.json: "dependencies": { "@my-org/utils": "workspace:^1.0.0" }
# workspace:* — always use local version
# workspace:^1.0.0 — use local version, but publish with ^1.0.0
4. Lockfile, Overrides & Catalog
pnpm-lock.yaml, overriding nested dependency versions, and the catalog feature
# pnpm-lock.yaml — always commit this file to git
# Unlike package-lock.json, pnpm's lockfile is compact and human-readable
# Check for lockfile drift:
pnpm install --frozen-lockfile # fails if pnpm-lock.yaml is out of date
# Use this in CI — ensures reproducible installs
# Override a nested dependency version (security patches, breaking bugs):
# package.json:
{
"pnpm": {
"overrides": {
"lodash": "^4.17.21", # pin all lodash to 4.17.21+
"axios@<1.0.0": "^1.6.0", # only override old axios versions
"my-app>react": "18.3.0" # override react only inside my-app
}
}
}
# Catalog — share dependency versions across workspace packages:
# pnpm-workspace.yaml:
catalog:
react: "^18.3.0"
typescript: "^5.7.0"
vitest: "^3.0.0"
# package.json in any workspace package:
{
"dependencies": { "react": "catalog:" },
"devDependencies": { "typescript": "catalog:", "vitest": "catalog:" }
}
# pnpm resolves "catalog:" to the version in pnpm-workspace.yaml
# One place to bump versions across all packages
5. CI, Docker & Migration from npm
pnpm in GitHub Actions, Docker best practices, and migrating from npm/Yarn
# GitHub Actions: # - uses: pnpm/action-setup@v4 # with: # version: 9 # - uses: actions/setup-node@v4 # with: # node-version: 22 # cache: "pnpm" # - run: pnpm install --frozen-lockfile # - run: pnpm test # Docker — efficient layer caching: FROM node:22-alpine RUN npm install -g pnpm@9 WORKDIR /app COPY package.json pnpm-lock.yaml ./ RUN pnpm install --frozen-lockfile --prod # install prod deps only COPY . . RUN pnpm build CMD ["node", "dist/index.js"] # Migration from npm: # 1. Delete node_modules and package-lock.json rm -rf node_modules package-lock.json # 2. Run pnpm import (converts package-lock.json to pnpm-lock.yaml): pnpm import # reads package-lock.json before deleting # 3. Install with pnpm: pnpm install # 4. Update scripts to use pnpm (CI, Makefile, README, Dockerfile) # Migration from Yarn: rm -rf node_modules yarn.lock pnpm import # converts yarn.lock to pnpm-lock.yaml pnpm install # Check for issues after migration: pnpm audit # security vulnerabilities pnpm outdated # outdated packages pnpm list --depth 0 # top-level dependencies
Track Node.js and package manager releases at ReleaseRun. Related: Node.js Reference | Turborepo Reference | Nx Reference | Nodejs EOL Tracker
🔍 Free tool: npm Package Health Checker — check any npm/pnpm package for EOL status, known CVEs, and active maintenance before adding to your project.
Founded
2023 in London, UK
Contact
hello@releaserun.com