
Pnpm
- 18k installs
- 5.7k repo stars
- Updated June 23, 2026
- antfu/skills
pnpm is a Node.js package manager that uses strict dependency resolution and content-addressable storage for efficiency.
About
Fast, disk-efficient package manager for Node.js with strict dependency resolution. Use when setting up monorepos, managing workspaces, or optimizing disk usage across multiple projects. Enforces dependency integrity by default.
- Strict dependency resolution prevents phantom dependencies
- Content-addressable storage for disk efficiency across projects
- Monorepo support with workspace filtering and catalogs
Pnpm by the numbers
- 18,046 all-time installs (skills.sh)
- +553 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #15 of 1,453 DevOps & CI/CD skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
pnpm capabilities & compatibility
- Works with
- github
- Use cases
- ci cd · devops
- Runs
- Runs locally
- Pricing
- Free
What pnpm says it does
pnpm enforces strict dependency resolution by default, preventing phantom dependencies.
It uses a content-addressable store to deduplicate packages across all projects on a machine, saving significant disk space.
npx skills add https://github.com/antfu/skills --skill pnpmAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 18k |
|---|---|
| repo stars | ★ 5.7k |
| Security audit | 2 / 3 scanners passed |
| Last updated | June 23, 2026 |
| Repository | antfu/skills ↗ |
How do you set up pnpm in GitHub Actions CI?
Configure and manage Node.js dependencies in monorepos using strict resolution and disk-efficient storage.
Who is it for?
Monorepo setups, workspace management, enforcing dependency integrity, reducing disk space usage
Skip if: Projects requiring strict npm/Yarn compatibility at the exact behavior level
When should I use this skill?
User asks to optimize pnpm in CI/CD, configure GitHub Actions for pnpm, or speed up monorepo install caches.
What you get
GitHub Actions YAML with pnpm/action-setup, Node cache integration, and version-pinned install/build steps
- GitHub Actions workflow YAML
- cached pnpm install steps
By the numbers
- pnpm 10.x or later supports strict dependency resolution by default
- Workspace catalogs enable centralized dependency version management
Files
pnpm is a fast, disk space efficient package manager. It uses a content-addressable store to deduplicate packages across all projects on a machine, saving significant disk space. pnpm enforces strict dependency resolution by default, preventing phantom dependencies. Configuration should preferably be placed in pnpm-workspace.yaml for pnpm-specific settings.
Important: When working with pnpm projects, agents should check for pnpm-workspace.yaml and .npmrc files to understand workspace structure and configuration. Always use --frozen-lockfile in CI environments.
The skill is based on pnpm 10.x, generated at 2026-01-28.
Core
| Topic | Description | Reference |
|---|---|---|
| CLI Commands | Install, add, remove, update, run, exec, dlx, and workspace commands | core-cli |
| Configuration | pnpm-workspace.yaml, .npmrc settings, and package.json fields | core-config |
| Workspaces | Monorepo support with filtering, workspace protocol, and shared lockfile | core-workspaces |
| Store | Content-addressable storage, hard links, and disk efficiency | core-store |
Features
| Topic | Description | Reference |
|---|---|---|
| Catalogs | Centralized dependency version management for workspaces | features-catalogs |
| Overrides | Force specific versions of dependencies including transitive | features-overrides |
| Patches | Modify third-party packages with custom fixes | features-patches |
| Aliases | Install packages under custom names using npm: protocol | features-aliases |
| Hooks | Customize resolution with .pnpmfile.cjs hooks | features-hooks |
| Peer Dependencies | Auto-install, strict mode, and dependency rules | features-peer-deps |
Best Practices
| Topic | Description | Reference |
|---|---|---|
| CI/CD Setup | GitHub Actions, GitLab CI, Docker, and caching strategies | best-practices-ci |
| Migration | Migrating from npm/Yarn, handling phantom deps, monorepo migration | best-practices-migration |
| Performance | Install optimizations, store caching, workspace parallelization | best-practices-performance |
Generation Info
- Source:
sources/pnpm - Git SHA:
a1d6d5aef9d5f369fa2f0d8a54f1edbaff8b23b3 - Generated: 2026-01-28
pnpm CI/CD Setup
Best practices for using pnpm in CI/CD environments for fast, reliable builds.
GitHub Actions
Basic Setup
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm test
- run: pnpm buildWith Store Caching
For larger projects, cache the pnpm store:
- uses: pnpm/action-setup@v4
with:
version: 9
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- run: pnpm install --frozen-lockfileMatrix Testing
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm testGitLab CI
image: node:20
stages:
- install
- test
- build
variables:
PNPM_HOME: /root/.local/share/pnpm
PATH: $PNPM_HOME:$PATH
before_script:
- corepack enable
- corepack prepare pnpm@latest --activate
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- .pnpm-store
install:
stage: install
script:
- pnpm config set store-dir .pnpm-store
- pnpm install --frozen-lockfile
test:
stage: test
script:
- pnpm test
build:
stage: build
script:
- pnpm buildDocker
Multi-Stage Build
# Build stage
FROM node:20-slim AS builder
# Enable corepack for pnpm
RUN corepack enable
WORKDIR /app
# Copy package files first for layer caching
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY packages/*/package.json ./packages/
# Install dependencies
RUN pnpm install --frozen-lockfile
# Copy source and build
COPY . .
RUN pnpm build
# Production stage
FROM node:20-slim AS runner
RUN corepack enable
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
COPY --from=builder /app/pnpm-lock.yaml ./
# Production install
RUN pnpm install --frozen-lockfile --prod
CMD ["node", "dist/index.js"]Optimized for Monorepos
FROM node:20-slim AS builder
RUN corepack enable
WORKDIR /app
# Copy workspace config
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
# Copy all package.json files maintaining structure
COPY packages/core/package.json ./packages/core/
COPY packages/api/package.json ./packages/api/
# Install all dependencies
RUN pnpm install --frozen-lockfile
# Copy source
COPY . .
# Build specific package
RUN pnpm --filter @myorg/api buildKey CI Flags
--frozen-lockfile
Always use in CI. Fails if pnpm-lock.yaml needs updates:
pnpm install --frozen-lockfile--prefer-offline
Use cached packages when available:
pnpm install --frozen-lockfile --prefer-offline--ignore-scripts
Skip lifecycle scripts for faster installs (use cautiously):
pnpm install --frozen-lockfile --ignore-scriptsCorepack Integration
Use Corepack to manage pnpm version:
// package.json
{
"packageManager": "pnpm@9.0.0"
}# GitHub Actions
- run: corepack enable
- run: pnpm install --frozen-lockfileMonorepo CI Strategies
Build Changed Packages Only
- name: Build changed packages
run: |
pnpm --filter "...[origin/main]" buildParallel Jobs per Package
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
packages: ${{ steps.changes.outputs.packages }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- id: changes
run: |
echo "packages=$(pnpm --filter '...[origin/main]' list --json | jq -c '[.[].name]')" >> $GITHUB_OUTPUT
test:
needs: detect-changes
if: needs.detect-changes.outputs.packages != '[]'
runs-on: ubuntu-latest
strategy:
matrix:
package: ${{ fromJson(needs.detect-changes.outputs.packages) }}
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- run: pnpm install --frozen-lockfile
- run: pnpm --filter ${{ matrix.package }} testBest Practices Summary
1. Always use `--frozen-lockfile` in CI 2. Cache the pnpm store for faster installs 3. Use Corepack for consistent pnpm versions 4. Specify `packageManager` in package.json 5. Use `--filter` in monorepos to build only what changed 6. Multi-stage Docker builds for smaller images
<!-- Source references:
- https://pnpm.io/continuous-integration
- https://github.com/pnpm/action-setup
-->
Migration to pnpm
Guide for migrating existing projects from npm or Yarn to pnpm.
Quick Migration
From npm
# Remove npm lockfile and node_modules
rm -rf node_modules package-lock.json
# Install with pnpm
pnpm installFrom Yarn
# Remove yarn lockfile and node_modules
rm -rf node_modules yarn.lock
# Install with pnpm
pnpm installImport Existing Lockfile
pnpm can import existing lockfiles:
# Import from npm or yarn lockfile
pnpm import
# This creates pnpm-lock.yaml from:
# - package-lock.json (npm)
# - yarn.lock (yarn)
# - npm-shrinkwrap.json (npm)Handling Common Issues
Phantom Dependencies
pnpm is strict about dependencies. If code imports a package not in package.json, it will fail.
Problem:
// Works with npm (hoisted), fails with pnpm
import lodash from 'lodash' // Not in dependencies, installed by another packageSolution: Add missing dependencies explicitly:
pnpm add lodashMissing Peer Dependencies
pnpm reports peer dependency issues by default.
Option 1: Let pnpm auto-install:
# .npmrc (default in pnpm v8+)
auto-install-peers=trueOption 2: Install manually:
pnpm add react react-domOption 3: Suppress warnings if acceptable:
{
"pnpm": {
"peerDependencyRules": {
"ignoreMissing": ["react"]
}
}
}Symlink Issues
Some tools don't work with symlinks. Use hoisted mode:
# .npmrc
node-linker=hoistedOr hoist specific packages:
public-hoist-pattern[]=*eslint*
public-hoist-pattern[]=*babel*Native Module Rebuilds
If native modules fail, try:
# Rebuild all native modules
pnpm rebuild
# Or reinstall
rm -rf node_modules
pnpm installMonorepo Migration
From npm Workspaces
1. Create pnpm-workspace.yaml:
packages:
- 'packages/*'2. Update internal dependencies to use workspace protocol:
{
"dependencies": {
"@myorg/utils": "workspace:^"
}
}3. Install:
rm -rf node_modules packages/*/node_modules package-lock.json
pnpm installFrom Yarn Workspaces
1. Remove Yarn-specific files:
rm yarn.lock .yarnrc.yml
rm -rf .yarn2. Create pnpm-workspace.yaml matching workspaces in package.json:
packages:
- 'packages/*'3. Update package.json - remove Yarn workspace config if not needed:
{
// Remove "workspaces" field (optional, pnpm uses pnpm-workspace.yaml)
}4. Convert workspace references:
// From Yarn
"@myorg/utils": "*"
// To pnpm
"@myorg/utils": "workspace:*"From Lerna
pnpm can replace Lerna for most use cases:
# Lerna: run script in all packages
lerna run build
# pnpm equivalent
pnpm -r run build
# Lerna: run in specific package
lerna run build --scope=@myorg/app
# pnpm equivalent
pnpm --filter @myorg/app run build
# Lerna: publish
lerna publish
# pnpm: use changesets instead
pnpm add -Dw @changesets/cli
pnpm changeset
pnpm changeset version
pnpm publish -rConfiguration Migration
.npmrc Settings
Most npm/Yarn settings work in pnpm's .npmrc:
# Registry settings (same as npm)
registry=https://registry.npmjs.org/
@myorg:registry=https://npm.myorg.com/
# Auth tokens (same as npm)
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
# pnpm-specific additions
auto-install-peers=true
strict-peer-dependencies=falseScripts Migration
Most scripts work unchanged. Update pnpm-specific patterns:
{
"scripts": {
// npm: recursive scripts
"build:all": "npm run build --workspaces",
// pnpm: use -r flag
"build:all": "pnpm -r run build",
// npm: run in specific workspace
"dev:app": "npm run dev -w packages/app",
// pnpm: use --filter
"dev:app": "pnpm --filter @myorg/app run dev"
}
}CI/CD Migration
Update CI configuration:
# Before (npm)
- run: npm ci
# After (pnpm)
- uses: pnpm/action-setup@v4
- run: pnpm install --frozen-lockfileAdd to package.json for Corepack:
{
"packageManager": "pnpm@9.0.0"
}Gradual Migration
For large projects, migrate gradually:
1. Start with CI: Use pnpm in CI, keep npm/yarn locally 2. Add pnpm-lock.yaml: Run pnpm import to create lockfile 3. Test thoroughly: Ensure builds work with pnpm 4. Update documentation: Update README, CONTRIBUTING 5. Remove old files: Delete old lockfiles after team adoption
Rollback Plan
If migration causes issues:
# Remove pnpm files
rm -rf node_modules pnpm-lock.yaml pnpm-workspace.yaml
# Restore npm
npm install
# Or restore Yarn
yarn installKeep old lockfile in git history for easy rollback.
<!-- Source references:
- https://pnpm.io/installation
- https://pnpm.io/cli/import
- https://pnpm.io/limitations
-->
pnpm Performance Optimization
pnpm is fast by default, but these optimizations can make it even faster.
Install Optimizations
Use Frozen Lockfile
Skip resolution when lockfile exists:
pnpm install --frozen-lockfileThis is faster because pnpm skips the resolution phase entirely.
Prefer Offline Mode
Use cached packages when available:
pnpm install --prefer-offlineOr configure globally:
# .npmrc
prefer-offline=trueSkip Optional Dependencies
If you don't need optional deps:
pnpm install --no-optionalSkip Scripts
For CI or when scripts aren't needed:
pnpm install --ignore-scriptsCaution: Some packages require postinstall scripts to work correctly.
Only Build Specific Dependencies
Only run build scripts for specific packages:
# .npmrc
onlyBuiltDependencies[]=esbuild
onlyBuiltDependencies[]=sharp
onlyBuiltDependencies[]=@swc/coreOr skip builds entirely for deps that don't need them:
{
"pnpm": {
"neverBuiltDependencies": ["fsevents", "cpu-features"]
}
}Store Optimizations
Side Effects Cache
Cache native module build results:
# .npmrc
side-effects-cache=trueThis caches the results of postinstall scripts, speeding up subsequent installs.
Shared Store
Use a single store for all projects (default behavior):
# .npmrc
store-dir=~/.pnpm-storeBenefits:
- Packages downloaded once for all projects
- Hard links save disk space
- Faster installs from cache
Store Maintenance
Periodically clean unused packages:
# Remove unreferenced packages
pnpm store prune
# Check store integrity
pnpm store statusWorkspace Optimizations
Parallel Execution
Run workspace scripts in parallel:
pnpm -r --parallel run buildControl concurrency:
# .npmrc
workspace-concurrency=8Stream Output
See output in real-time:
pnpm -r --stream run buildFilter to Changed Packages
Only build what changed:
# Build packages changed since main branch
pnpm --filter "...[origin/main]" run buildTopological Order
Build dependencies before dependents:
pnpm -r run build
# Automatically runs in topological orderFor explicit sequential builds:
pnpm -r --workspace-concurrency=1 run buildNetwork Optimizations
Configure Registry
Use closest/fastest registry:
# .npmrc
registry=https://registry.npmmirror.com/HTTP Settings
Tune network settings:
# .npmrc
fetch-retries=3
fetch-retry-mintimeout=10000
fetch-retry-maxtimeout=60000
network-concurrency=16Proxy Configuration
# .npmrc
proxy=http://proxy.company.com:8080
https-proxy=http://proxy.company.com:8080Lockfile Optimization
Single Lockfile (Monorepos)
Use shared lockfile for all packages (default):
# .npmrc
shared-workspace-lockfile=trueBenefits:
- Single source of truth
- Faster resolution
- Consistent versions across workspace
Lockfile-only Mode
Only update lockfile without installing:
pnpm install --lockfile-onlyBenchmarking
Compare Install Times
# Clean install
rm -rf node_modules pnpm-lock.yaml
time pnpm install
# Cached install (with lockfile)
rm -rf node_modules
time pnpm install --frozen-lockfile
# With store cache
time pnpm install --frozen-lockfile --prefer-offlineProfile Resolution
Debug slow installs:
# Verbose logging
pnpm install --reporter=append-only
# Debug mode
DEBUG=pnpm:* pnpm installConfiguration Summary
Optimized .npmrc for performance:
# Install behavior
prefer-offline=true
auto-install-peers=true
# Build optimization
side-effects-cache=true
# Only build what's necessary
onlyBuiltDependencies[]=esbuild
onlyBuiltDependencies[]=@swc/core
# Network
fetch-retries=3
network-concurrency=16
# Workspace
workspace-concurrency=4Quick Reference
| Scenario | Command/Setting |
|---|---|
| CI installs | pnpm install --frozen-lockfile |
| Offline development | --prefer-offline |
| Skip native builds | neverBuiltDependencies |
| Parallel workspace | pnpm -r --parallel run build |
| Build changed only | pnpm --filter "...[origin/main]" build |
| Clean store | pnpm store prune |
<!-- Source references:
- https://pnpm.io/npmrc
- https://pnpm.io/cli/install
- https://pnpm.io/filtering
-->
pnpm CLI Commands
pnpm provides a comprehensive CLI for package management with commands similar to npm/yarn but with unique features.
Installation Commands
Install all dependencies
pnpm install
# or
pnpm iAdd a dependency
# Production dependency
pnpm add <pkg>
# Dev dependency
pnpm add -D <pkg>
pnpm add --save-dev <pkg>
# Optional dependency
pnpm add -O <pkg>
# Global package
pnpm add -g <pkg>
# Specific version
pnpm add <pkg>@<version>
pnpm add <pkg>@next
pnpm add <pkg>@^1.0.0Remove a dependency
pnpm remove <pkg>
pnpm rm <pkg>
pnpm uninstall <pkg>
pnpm un <pkg>Update dependencies
# Update all
pnpm update
pnpm up
# Update specific package
pnpm update <pkg>
# Update to latest (ignore semver)
pnpm update --latest
pnpm up -L
# Interactive update
pnpm update --interactive
pnpm up -iScript Commands
Run scripts
pnpm run <script>
# or shorthand
pnpm <script>
# Pass arguments to script
pnpm run build -- --watch
# Run script if exists (no error if missing)
pnpm run --if-present buildExecute binaries
# Run local binary
pnpm exec <command>
# Example
pnpm exec eslint .dlx - Run without installing
# Like npx but for pnpm
pnpm dlx <pkg>
# Examples
pnpm dlx create-vite my-app
pnpm dlx degit user/repo my-projectWorkspace Commands
Run in all packages
# Run script in all workspace packages
pnpm -r run <script>
pnpm --recursive run <script>
# Run in specific packages
pnpm --filter <pattern> run <script>
# Examples
pnpm --filter "./packages/**" run build
pnpm --filter "!./packages/internal/**" run test
pnpm --filter "@myorg/*" run lintFilter patterns
# By package name
pnpm --filter <pkg-name> <command>
pnpm --filter "@scope/pkg" build
# By directory
pnpm --filter "./packages/core" test
# Dependencies of a package
pnpm --filter "...@scope/app" build
# Dependents of a package
pnpm --filter "@scope/core..." test
# Changed packages since commit/branch
pnpm --filter "...[origin/main]" buildOther Useful Commands
Link packages
# Link global package
pnpm link --global
pnpm link -g
# Use linked package
pnpm link --global <pkg>Patch packages
# Create patch for a package
pnpm patch <pkg>@<version>
# After editing, commit the patch
pnpm patch-commit <path>
# Remove a patch
pnpm patch-remove <pkg>Store management
# Show store path
pnpm store path
# Remove unreferenced packages
pnpm store prune
# Check store integrity
pnpm store statusOther commands
# Clean install (like npm ci)
pnpm install --frozen-lockfile
# List installed packages
pnpm list
pnpm ls
# Why is package installed?
pnpm why <pkg>
# Outdated packages
pnpm outdated
# Audit for vulnerabilities
pnpm audit
# Rebuild native modules
pnpm rebuild
# Import from npm/yarn lockfile
pnpm import
# Create tarball
pnpm pack
# Publish package
pnpm publishUseful Flags
# Ignore scripts
pnpm install --ignore-scripts
# Prefer offline (use cache)
pnpm install --prefer-offline
# Strict peer dependencies
pnpm install --strict-peer-dependencies
# Production only
pnpm install --prod
pnpm install -P
# No optional dependencies
pnpm install --no-optional<!-- Source references:
- https://pnpm.io/cli/install
- https://pnpm.io/cli/add
- https://pnpm.io/cli/run
- https://pnpm.io/filtering
-->
pnpm Configuration
pnpm uses two main configuration files: pnpm-workspace.yaml for workspace and pnpm-specific settings, and .npmrc for npm-compatible and pnpm-specific settings.
pnpm-workspace.yaml
The recommended location for pnpm-specific configurations. Place at project root.
# Define workspace packages
packages:
- 'packages/*'
- 'apps/*'
- '!**/test/**' # Exclude pattern
# Catalog for shared dependency versions
catalog:
react: ^18.2.0
typescript: ~5.3.0
# Named catalogs for different dependency groups
catalogs:
react17:
react: ^17.0.2
react-dom: ^17.0.2
react18:
react: ^18.2.0
react-dom: ^18.2.0
# Override resolutions (preferred location)
overrides:
lodash: ^4.17.21
'foo@^1.0.0>bar': ^2.0.0
# pnpm settings (alternative to .npmrc)
settings:
auto-install-peers: true
strict-peer-dependencies: false
link-workspace-packages: true
prefer-workspace-packages: true
shared-workspace-lockfile: true.npmrc Settings
pnpm reads settings from .npmrc files. Create at project root or user home.
Common pnpm Settings
# Automatically install peer dependencies
auto-install-peers=true
# Fail on peer dependency issues
strict-peer-dependencies=false
# Hoist patterns for dependencies
public-hoist-pattern[]=*types*
public-hoist-pattern[]=*eslint*
shamefully-hoist=false
# Store location
store-dir=~/.pnpm-store
# Virtual store location
virtual-store-dir=node_modules/.pnpm
# Lockfile settings
lockfile=true
prefer-frozen-lockfile=true
# Side effects cache (speeds up rebuilds)
side-effects-cache=true
# Registry settings
registry=https://registry.npmjs.org/
@myorg:registry=https://npm.myorg.com/Workspace Settings
# Link workspace packages
link-workspace-packages=true
# Prefer workspace packages over registry
prefer-workspace-packages=true
# Single lockfile for all packages
shared-workspace-lockfile=true
# Save prefix for workspace dependencies
save-workspace-protocol=rollingNode.js Settings
# Use specific Node.js version
use-node-version=20.10.0
# Node.js version file
node-version-file=.nvmrc
# Manage Node.js versions
manage-package-manager-versions=trueSecurity Settings
# Ignore specific scripts
ignore-scripts=false
# Allow specific build scripts
onlyBuiltDependencies[]=esbuild
onlyBuiltDependencies[]=sharp
# Package extensions for missing peer deps
package-extensions[foo@1].peerDependencies.bar=*Configuration Hierarchy
Settings are read in order (later overrides earlier):
1. /etc/npmrc - Global config 2. ~/.npmrc - User config 3. <project>/.npmrc - Project config 4. Environment variables: npm_config_<key>=<value> 5. pnpm-workspace.yaml settings field
Environment Variables
# Set config via env
npm_config_registry=https://registry.npmjs.org/
# pnpm-specific env vars
PNPM_HOME=~/.local/share/pnpmPackage.json Fields
pnpm reads specific fields from package.json:
{
"pnpm": {
"overrides": {
"lodash": "^4.17.21"
},
"peerDependencyRules": {
"ignoreMissing": ["@babel/*"],
"allowedVersions": {
"react": "17 || 18"
}
},
"neverBuiltDependencies": ["fsevents"],
"onlyBuiltDependencies": ["esbuild"],
"allowedDeprecatedVersions": {
"request": "*"
},
"patchedDependencies": {
"express@4.18.2": "patches/express@4.18.2.patch"
}
}
}Key Differences from npm/yarn
1. Strict by default: No phantom dependencies 2. Workspace protocol: workspace:* for local packages 3. Catalogs: Centralized version management 4. Content-addressable store: Shared across projects
<!-- Source references:
- https://pnpm.io/pnpm-workspace_yaml
- https://pnpm.io/npmrc
- https://pnpm.io/package_json
-->
pnpm Store
pnpm uses a content-addressable store to save disk space and speed up installations. All packages are stored once globally and hard-linked to project node_modules.
How It Works
1. Global Store: Packages are downloaded once to a central store 2. Hard Links: Projects link to store instead of copying files 3. Content-Addressable: Files are stored by content hash, deduplicating identical files
Storage Layout
~/.pnpm-store/ # Global store (default location)
└── v3/
└── files/
└── <hash>/ # Files stored by content hash
project/
└── node_modules/
├── .pnpm/ # Virtual store (hard links to global store)
│ ├── lodash@4.17.21/
│ │ └── node_modules/
│ │ └── lodash/
│ └── express@4.18.2/
│ └── node_modules/
│ ├── express/
│ └── <deps>/ # Flat structure for dependencies
├── lodash -> .pnpm/lodash@4.17.21/node_modules/lodash
└── express -> .pnpm/express@4.18.2/node_modules/expressStore Commands
# Show store location
pnpm store path
# Remove unreferenced packages
pnpm store prune
# Check store integrity
pnpm store status
# Add package to store without installing
pnpm store add <pkg>Configuration
Store Location
# .npmrc
store-dir=~/.pnpm-store
# Or use environment variable
PNPM_HOME=~/.local/share/pnpmVirtual Store
The virtual store (.pnpm in node_modules) contains symlinks to the global store:
# Customize virtual store location
virtual-store-dir=node_modules/.pnpm
# Alternative flat layout
node-linker=hoistedDisk Space Benefits
pnpm saves significant disk space:
- Deduplication: Same package version stored once across all projects
- Content deduplication: Identical files across different packages stored once
- Hard links: No copying, just linking
Check disk usage
# Compare actual vs apparent size
du -sh node_modules # Apparent size
du -sh --apparent-size node_modules # With hard links countedNode Linker Modes
Configure how node_modules is structured:
# Default: Symlinked structure (recommended)
node-linker=isolated
# Flat node_modules (npm-like, for compatibility)
node-linker=hoisted
# PnP mode (experimental, like Yarn PnP)
node-linker=pnpIsolated Mode (Default)
- Strict dependency resolution
- No phantom dependencies
- Packages can only access declared dependencies
Hoisted Mode
- Flat
node_moduleslike npm - For compatibility with tools that don't support symlinks
- Loses strictness benefits
Side Effects Cache
Cache build outputs for native modules:
# Enable side effects caching
side-effects-cache=true
# Store side effects in project (instead of global store)
side-effects-cache-readonly=trueShared Store Across Machines
For CI/CD, you can share the store:
# GitHub Actions example
- uses: pnpm/action-setup@v4
with:
run_install: false
- name: Get pnpm store directory
shell: bash
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}Troubleshooting
Store corruption
# Verify and fix store
pnpm store status
pnpm store pruneHard link issues (network drives, Docker)
# Use copying instead of hard links
package-import-method=copyPermission issues
# Fix store permissions
chmod -R u+w ~/.pnpm-store<!-- Source references:
- https://pnpm.io/symlinked-node-modules-structure
- https://pnpm.io/cli/store
- https://pnpm.io/npmrc#store-dir
-->
pnpm Workspaces
pnpm has built-in support for monorepos (multi-package repositories) through workspaces.
Setting Up Workspaces
Create pnpm-workspace.yaml at the repository root:
packages:
# Include all packages in packages/ directory
- 'packages/*'
# Include all apps
- 'apps/*'
# Include nested packages
- 'tools/*/packages/*'
# Exclude test directories
- '!**/test/**'Workspace Protocol
Use workspace: protocol to reference local packages:
{
"dependencies": {
"@myorg/utils": "workspace:*",
"@myorg/core": "workspace:^",
"@myorg/types": "workspace:~"
}
}Protocol Variants
| Protocol | Behavior | Published As |
|---|---|---|
workspace:* | Any version | Actual version (e.g., 1.2.3) |
workspace:^ | Compatible version | ^1.2.3 |
workspace:~ | Patch version | ~1.2.3 |
workspace:^1.0.0 | Semver range | ^1.0.0 |
Filtering Packages
Run commands on specific packages using --filter:
# By package name
pnpm --filter @myorg/app build
pnpm -F @myorg/app build
# By directory path
pnpm --filter "./packages/core" test
# Glob patterns
pnpm --filter "@myorg/*" lint
pnpm --filter "!@myorg/internal-*" publish
# All packages
pnpm -r build
pnpm --recursive buildDependency-based Filtering
# Package and all its dependencies
pnpm --filter "...@myorg/app" build
# Package and all its dependents
pnpm --filter "@myorg/core..." test
# Both directions
pnpm --filter "...@myorg/shared..." build
# Changed since git ref
pnpm --filter "...[origin/main]" test
pnpm --filter "[HEAD~5]" lintWorkspace Commands
Install dependencies
# Install all workspace packages
pnpm install
# Add dependency to specific package
pnpm --filter @myorg/app add lodash
# Add workspace dependency
pnpm --filter @myorg/app add @myorg/utilsRun scripts
# Run in all packages with that script
pnpm -r run build
# Run in topological order (dependencies first)
pnpm -r --workspace-concurrency=1 run build
# Run in parallel
pnpm -r --parallel run test
# Stream output
pnpm -r --stream run devExecute commands
# Run command in all packages
pnpm -r exec pwd
# Run in specific packages
pnpm --filter "./packages/**" exec rm -rf distWorkspace Settings
Configure in .npmrc or pnpm-workspace.yaml:
# Link workspace packages automatically
link-workspace-packages=true
# Prefer workspace packages over registry
prefer-workspace-packages=true
# Single lockfile (recommended)
shared-workspace-lockfile=true
# Workspace protocol handling
save-workspace-protocol=rolling
# Concurrent workspace scripts
workspace-concurrency=4Publishing Workspaces
When publishing, workspace: protocols are converted:
// Before publish
{
"dependencies": {
"@myorg/utils": "workspace:^"
}
}
// After publish
{
"dependencies": {
"@myorg/utils": "^1.2.3"
}
}Use --no-git-checks for publishing from CI:
pnpm publish -r --no-git-checksBest Practices
1. Use workspace protocol for internal dependencies 2. Enable `link-workspace-packages` for automatic linking 3. Use shared lockfile for consistency 4. Filter by dependencies when building to ensure correct order 5. Use catalogs for shared external dependency versions
Example Project Structure
my-monorepo/
├── pnpm-workspace.yaml
├── package.json
├── pnpm-lock.yaml
├── packages/
│ ├── core/
│ │ └── package.json
│ ├── utils/
│ │ └── package.json
│ └── types/
│ └── package.json
└── apps/
├── web/
│ └── package.json
└── api/
└── package.json<!-- Source references:
- https://pnpm.io/workspaces
- https://pnpm.io/filtering
- https://pnpm.io/npmrc#workspace-settings
-->
pnpm Aliases
pnpm supports package aliases using the npm: protocol. This lets you install packages under different names, use multiple versions of the same package, or substitute packages.
Basic Syntax
pnpm add <alias>@npm:<package>@<version>In package.json:
{
"dependencies": {
"<alias>": "npm:<package>@<version>"
}
}Use Cases
Multiple Versions of Same Package
Install different versions side by side:
{
"dependencies": {
"lodash3": "npm:lodash@3",
"lodash4": "npm:lodash@4"
}
}Usage:
import lodash3 from 'lodash3'
import lodash4 from 'lodash4'Replace Package with Fork
Substitute a package with a fork or alternative:
{
"dependencies": {
"original-pkg": "npm:my-fork@^1.0.0"
}
}All imports of original-pkg will resolve to my-fork.
Replace Deprecated Package
{
"dependencies": {
"request": "npm:@cypress/request@^3.0.0"
}
}Scoped to Unscoped (or vice versa)
{
"dependencies": {
"vue": "npm:@anthropic/vue@^3.0.0",
"@myorg/utils": "npm:lodash@^4.17.21"
}
}CLI Usage
Add with alias
# Add lodash under alias
pnpm add lodash4@npm:lodash@4
# Add fork as original name
pnpm add request@npm:@cypress/requestAdd multiple versions
pnpm add react17@npm:react@17 react18@npm:react@18With TypeScript
For type resolution with aliases, you may need to configure TypeScript:
// tsconfig.json
{
"compilerOptions": {
"paths": {
"lodash3": ["node_modules/lodash3"],
"lodash4": ["node_modules/lodash4"]
}
}
}Or use @types packages with aliases:
{
"devDependencies": {
"@types/lodash3": "npm:@types/lodash@3",
"@types/lodash4": "npm:@types/lodash@4"
}
}Combined with Overrides
Force all transitive dependencies to use an alias:
# pnpm-workspace.yaml
overrides:
"underscore": "npm:lodash@^4.17.21"This replaces all underscore imports (including in dependencies) with lodash.
Git and Local Aliases
Aliases work with any valid pnpm specifier:
{
"dependencies": {
"my-fork": "npm:user/repo#commit",
"local-pkg": "file:../local-package"
}
}Best Practices
1. Clear naming: Use descriptive alias names that indicate purpose
"lodash-legacy": "npm:lodash@3"
"lodash-modern": "npm:lodash@4"2. Document aliases: Add comments or documentation explaining why aliases exist
3. Prefer overrides for global replacement: If you want to replace a package everywhere, use overrides instead of aliases
4. Test thoroughly: Aliased packages may have subtle differences in behavior
<!-- Source references:
- https://pnpm.io/aliases
-->
pnpm Catalogs
Catalogs provide a centralized way to manage dependency versions across a workspace. Define versions once, use everywhere.
Basic Usage
Define a catalog in pnpm-workspace.yaml:
packages:
- 'packages/*'
catalog:
react: ^18.2.0
react-dom: ^18.2.0
typescript: ~5.3.0
vite: ^5.0.0Reference in package.json with catalog::
{
"dependencies": {
"react": "catalog:",
"react-dom": "catalog:"
},
"devDependencies": {
"typescript": "catalog:",
"vite": "catalog:"
}
}Named Catalogs
Create multiple catalogs for different scenarios:
packages:
- 'packages/*'
# Default catalog
catalog:
lodash: ^4.17.21
# Named catalogs
catalogs:
react17:
react: ^17.0.2
react-dom: ^17.0.2
react18:
react: ^18.2.0
react-dom: ^18.2.0
testing:
vitest: ^1.0.0
"@testing-library/react": ^14.0.0Reference named catalogs:
{
"dependencies": {
"react": "catalog:react18",
"react-dom": "catalog:react18"
},
"devDependencies": {
"vitest": "catalog:testing"
}
}Benefits
1. Single source of truth: Update version in one place 2. Consistency: All packages use the same version 3. Easy upgrades: Change version once, affects entire workspace 4. Type-safe: TypeScript support in pnpm-workspace.yaml
Catalog vs Overrides
| Feature | Catalogs | Overrides |
|---|---|---|
| Purpose | Define versions for direct dependencies | Force versions for any dependency |
| Scope | Direct dependencies only | All dependencies (including transitive) |
| Usage | "pkg": "catalog:" | Applied automatically |
| Opt-in | Explicit per package.json | Global to workspace |
Publishing with Catalogs
When publishing, catalog: references are replaced with actual versions:
// Before publish (source)
{
"dependencies": {
"react": "catalog:"
}
}
// After publish (published package)
{
"dependencies": {
"react": "^18.2.0"
}
}Migration from Overrides
If you're using overrides for version consistency:
# Before (using overrides)
overrides:
react: ^18.2.0
react-dom: ^18.2.0Migrate to catalogs for cleaner dependency management:
# After (using catalogs)
catalog:
react: ^18.2.0
react-dom: ^18.2.0Then update package.json files to use catalog:.
Best Practices
1. Use default catalog for commonly shared dependencies 2. Use named catalogs for version variants (e.g., different React versions) 3. Keep catalog minimal - only include shared dependencies 4. Combine with workspace protocol for internal packages
catalog:
# External shared dependencies
lodash: ^4.17.21
zod: ^3.22.0
# Internal packages use workspace: protocol instead
# "dependencies": { "@myorg/utils": "workspace:^" }<!-- Source references:
- https://pnpm.io/catalogs
-->
pnpm Hooks
pnpm provides hooks via .pnpmfile.cjs to customize how packages are resolved and their metadata is processed.
Setup
Create .pnpmfile.cjs at workspace root:
// .pnpmfile.cjs
function readPackage(pkg, context) {
// Modify package metadata
return pkg
}
function afterAllResolved(lockfile, context) {
// Modify lockfile
return lockfile
}
module.exports = {
hooks: {
readPackage,
afterAllResolved
}
}readPackage Hook
Called for every package before resolution. Use to modify dependencies, add missing peer deps, or fix broken packages.
Add Missing Peer Dependency
function readPackage(pkg, context) {
if (pkg.name === 'some-broken-package') {
pkg.peerDependencies = {
...pkg.peerDependencies,
react: '*'
}
context.log(`Added react peer dep to ${pkg.name}`)
}
return pkg
}Override Dependency Version
function readPackage(pkg, context) {
// Fix all lodash versions
if (pkg.dependencies?.lodash) {
pkg.dependencies.lodash = '^4.17.21'
}
if (pkg.devDependencies?.lodash) {
pkg.devDependencies.lodash = '^4.17.21'
}
return pkg
}Remove Unwanted Dependency
function readPackage(pkg, context) {
// Remove optional dependency that causes issues
if (pkg.optionalDependencies?.fsevents) {
delete pkg.optionalDependencies.fsevents
}
return pkg
}Replace Package
function readPackage(pkg, context) {
// Replace deprecated package
if (pkg.dependencies?.['old-package']) {
pkg.dependencies['new-package'] = pkg.dependencies['old-package']
delete pkg.dependencies['old-package']
}
return pkg
}Fix Broken Package
function readPackage(pkg, context) {
// Fix incorrect exports field
if (pkg.name === 'broken-esm-package') {
pkg.exports = {
'.': {
import: './dist/index.mjs',
require: './dist/index.cjs'
}
}
}
return pkg
}afterAllResolved Hook
Called after the lockfile is generated. Use for post-resolution modifications.
function afterAllResolved(lockfile, context) {
// Log all resolved packages
context.log(`Resolved ${Object.keys(lockfile.packages || {}).length} packages`)
// Modify lockfile if needed
return lockfile
}Context Object
The context object provides utilities:
function readPackage(pkg, context) {
// Log messages
context.log('Processing package...')
return pkg
}Use with TypeScript
For type hints, use JSDoc:
// .pnpmfile.cjs
/**
* @param {import('type-fest').PackageJson} pkg
* @param {{ log: (msg: string) => void }} context
* @returns {import('type-fest').PackageJson}
*/
function readPackage(pkg, context) {
return pkg
}
module.exports = {
hooks: {
readPackage
}
}Common Patterns
Conditional by Package Name
function readPackage(pkg, context) {
switch (pkg.name) {
case 'package-a':
pkg.dependencies.foo = '^2.0.0'
break
case 'package-b':
delete pkg.optionalDependencies.bar
break
}
return pkg
}Apply to All Packages
function readPackage(pkg, context) {
// Remove all optional fsevents
if (pkg.optionalDependencies) {
delete pkg.optionalDependencies.fsevents
}
return pkg
}Debug Resolution
function readPackage(pkg, context) {
if (process.env.DEBUG_PNPM) {
context.log(`${pkg.name}@${pkg.version}`)
context.log(` deps: ${Object.keys(pkg.dependencies || {}).join(', ')}`)
}
return pkg
}Hooks vs Overrides
| Feature | Hooks (.pnpmfile.cjs) | Overrides |
|---|---|---|
| Complexity | Can use JavaScript logic | Declarative only |
| Scope | Any package metadata | Version only |
| Use case | Complex fixes, conditional logic | Simple version pins |
Prefer overrides for simple version fixes. Use hooks when you need:
- Conditional logic
- Non-version modifications (exports, peer deps)
- Logging/debugging
Troubleshooting
Hook not running
1. Ensure file is named .pnpmfile.cjs (not .js) 2. Check file is at workspace root 3. Run pnpm install to trigger hooks
Debug hooks
# See hook logs
pnpm install --reporter=append-only<!-- Source references:
- https://pnpm.io/pnpmfile
-->
pnpm Overrides
Overrides let you force specific versions of packages, including transitive dependencies. Useful for fixing security vulnerabilities or compatibility issues.
Basic Syntax
Define overrides in pnpm-workspace.yaml (recommended) or package.json:
In pnpm-workspace.yaml (Recommended)
packages:
- 'packages/*'
overrides:
# Override all versions of a package
lodash: ^4.17.21
# Override specific version range
"foo@^1.0.0": ^1.2.3
# Override nested dependency
"express>cookie": ^0.6.0
# Override to different package
"underscore": "npm:lodash@^4.17.21"In package.json
{
"pnpm": {
"overrides": {
"lodash": "^4.17.21",
"foo@^1.0.0": "^1.2.3",
"bar@^2.0.0>qux": "^1.0.0"
}
}
}Override Patterns
Override all instances
overrides:
lodash: ^4.17.21Forces all lodash installations to use ^4.17.21.
Override specific parent version
overrides:
"foo@^1.0.0": ^1.2.3Only override foo when the requested version matches ^1.0.0.
Override nested dependency
overrides:
"express>cookie": ^0.6.0
"foo@1.x>bar@^2.0.0>qux": ^1.0.0Override cookie only when it's a dependency of express.
Replace with different package
overrides:
# Replace underscore with lodash
"underscore": "npm:lodash@^4.17.21"
# Use local file
"some-pkg": "file:./local-pkg"
# Use git
"some-pkg": "github:user/repo#commit"Remove a dependency
overrides:
"unwanted-pkg": "-"The - removes the package entirely.
Common Use Cases
Security Fix
Force patched version of vulnerable package:
overrides:
# Fix CVE in transitive dependency
"minimist": "^1.2.6"
"json5": "^2.2.3"Deduplicate Dependencies
Force single version when multiple are installed:
overrides:
"react": "^18.2.0"
"react-dom": "^18.2.0"Fix Peer Dependency Issues
overrides:
"@types/react": "^18.2.0"Replace Deprecated Package
overrides:
"request": "npm:@cypress/request@^3.0.0"Hooks Alternative
For more complex scenarios, use .pnpmfile.cjs:
// .pnpmfile.cjs
function readPackage(pkg, context) {
// Override dependency version
if (pkg.dependencies?.lodash) {
pkg.dependencies.lodash = '^4.17.21'
}
// Add missing peer dependency
if (pkg.name === 'some-package') {
pkg.peerDependencies = {
...pkg.peerDependencies,
react: '*'
}
}
return pkg
}
module.exports = {
hooks: {
readPackage
}
}Overrides vs Catalogs
| Feature | Overrides | Catalogs |
|---|---|---|
| Affects | All dependencies (including transitive) | Direct dependencies only |
| Usage | Automatic | Explicit catalog: reference |
| Purpose | Force versions, fix issues | Version management |
| Granularity | Can target specific parents | Package-wide only |
Debugging
Check which version is resolved:
# See resolved versions
pnpm why lodash
# List all versions
pnpm list lodash --depth=Infinity<!-- Source references:
- https://pnpm.io/package_json#pnpmoverrides
- https://pnpm.io/pnpmfile
-->
pnpm Patches
pnpm's patching feature lets you modify third-party packages directly. Useful for applying fixes before upstream releases or customizing package behavior.
Creating a Patch
Step 1: Initialize Patch
pnpm patch <pkg>@<version>
# Example
pnpm patch express@4.18.2This creates a temporary directory with the package source and outputs the path:
You can now edit the following folder: /tmp/abc123...Step 2: Edit Files
Navigate to the temporary directory and make your changes:
cd /tmp/abc123...
# Edit files as neededStep 3: Commit Patch
pnpm patch-commit <path-from-step-1>
# Example
pnpm patch-commit /tmp/abc123...This creates a .patch file in patches/ and updates package.json:
patches/
└── express@4.18.2.patch{
"pnpm": {
"patchedDependencies": {
"express@4.18.2": "patches/express@4.18.2.patch"
}
}
}Patch File Format
Patches use standard unified diff format:
diff --git a/lib/router/index.js b/lib/router/index.js
index abc123..def456 100644
--- a/lib/router/index.js
+++ b/lib/router/index.js
@@ -100,6 +100,7 @@ function createRouter() {
// Original code
- const timeout = 30000;
+ const timeout = 60000; // Extended timeout
return router;
}Managing Patches
List Patched Packages
pnpm list --depth=0
# Shows (patched) marker for patched packagesUpdate a Patch
# Edit existing patch
pnpm patch express@4.18.2
# After editing
pnpm patch-commit <path>Remove a Patch
pnpm patch-remove <pkg>@<version>
# Example
pnpm patch-remove express@4.18.2Or manually: 1. Delete the patch file from patches/ 2. Remove entry from patchedDependencies in package.json 3. Run pnpm install
Patch Configuration
Custom Patches Directory
{
"pnpm": {
"patchedDependencies": {
"express@4.18.2": "custom-patches/my-express-fix.patch"
}
}
}Multiple Packages
{
"pnpm": {
"patchedDependencies": {
"express@4.18.2": "patches/express@4.18.2.patch",
"lodash@4.17.21": "patches/lodash@4.17.21.patch",
"@types/node@20.10.0": "patches/@types__node@20.10.0.patch"
}
}
}Workspaces
Patches are shared across the workspace. Define in the root package.json:
// Root package.json
{
"pnpm": {
"patchedDependencies": {
"express@4.18.2": "patches/express@4.18.2.patch"
}
}
}All workspace packages using express@4.18.2 will have the patch applied.
Best Practices
1. Version specificity: Patches are tied to exact versions. Update patches when upgrading dependencies.
2. Document patches: Add comments explaining why the patch exists:
# In patches/README.md
## express@4.18.2.patch
Fixes timeout issue. PR pending: https://github.com/expressjs/express/pull/12343. Minimize patches: Keep patches small and focused. Large patches are hard to maintain.
4. Track upstream: Note upstream issues/PRs so you can remove patches when fixed.
5. Test patches: Ensure patched code works correctly in your use case.
Troubleshooting
Patch fails to apply
ERR_PNPM_PATCH_FAILED Cannot apply patchThe package version changed. Recreate the patch:
pnpm patch-remove express@4.18.2
pnpm patch express@4.18.2
# Reapply changes
pnpm patch-commit <path>Patch not applied
Ensure: 1. Version in patchedDependencies matches installed version exactly 2. Run pnpm install after adding patch configuration
<!-- Source references:
- https://pnpm.io/cli/patch
- https://pnpm.io/cli/patch-commit
- https://pnpm.io/package_json#pnpmpatcheddependencies
-->
pnpm Peer Dependencies
pnpm has strict peer dependency handling by default. It provides configuration options to control how peer dependencies are resolved and reported.
Auto-Install Peer Dependencies
By default, pnpm automatically installs peer dependencies:
# .npmrc (default is true since pnpm v8)
auto-install-peers=trueWhen enabled, pnpm automatically adds missing peer dependencies based on the best matching version.
Strict Peer Dependencies
Control whether peer dependency issues cause errors:
# Fail on peer dependency issues (default: false)
strict-peer-dependencies=trueWhen strict, pnpm will fail if:
- Peer dependency is missing
- Installed version doesn't match required range
Peer Dependency Rules
Configure peer dependency behavior in package.json:
{
"pnpm": {
"peerDependencyRules": {
"ignoreMissing": ["@babel/*", "eslint"],
"allowedVersions": {
"react": "17 || 18"
},
"allowAny": ["@types/*"]
}
}
}ignoreMissing
Suppress warnings for missing peer dependencies:
{
"pnpm": {
"peerDependencyRules": {
"ignoreMissing": [
"@babel/*",
"eslint",
"webpack"
]
}
}
}Use patterns:
"react"- exact package name"@babel/*"- all packages in scope"*"- all packages (not recommended)
allowedVersions
Allow specific versions that would otherwise cause warnings:
{
"pnpm": {
"peerDependencyRules": {
"allowedVersions": {
"react": "17 || 18",
"webpack": "4 || 5",
"@types/react": "*"
}
}
}
}allowAny
Allow any version for specified peer dependencies:
{
"pnpm": {
"peerDependencyRules": {
"allowAny": ["@types/*", "eslint"]
}
}
}Adding Peer Dependencies via Hooks
Use .pnpmfile.cjs to add missing peer dependencies:
// .pnpmfile.cjs
function readPackage(pkg, context) {
// Add missing peer dependency
if (pkg.name === 'problematic-package') {
pkg.peerDependencies = {
...pkg.peerDependencies,
react: '*'
}
}
return pkg
}
module.exports = {
hooks: {
readPackage
}
}Peer Dependencies in Workspaces
Workspace packages can satisfy peer dependencies:
// packages/app/package.json
{
"dependencies": {
"react": "^18.2.0",
"@myorg/components": "workspace:^"
}
}
// packages/components/package.json
{
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0"
}
}The workspace app provides react which satisfies components' peer dependency.
Common Scenarios
Monorepo with Shared React
# pnpm-workspace.yaml
catalog:
react: ^18.2.0
react-dom: ^18.2.0// packages/ui/package.json
{
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
}
// apps/web/package.json
{
"dependencies": {
"react": "catalog:",
"react-dom": "catalog:",
"@myorg/ui": "workspace:^"
}
}Suppress ESLint Plugin Warnings
{
"pnpm": {
"peerDependencyRules": {
"ignoreMissing": [
"eslint",
"@typescript-eslint/parser"
]
}
}
}Allow Multiple Major Versions
{
"pnpm": {
"peerDependencyRules": {
"allowedVersions": {
"webpack": "4 || 5",
"postcss": "7 || 8"
}
}
}
}Debugging Peer Dependencies
# See why a package is installed
pnpm why <package>
# List all peer dependency warnings
pnpm install --reporter=append-only 2>&1 | grep -i peer
# Check dependency tree
pnpm list --depth=InfinityBest Practices
1. Enable auto-install-peers for convenience (default in pnpm v8+)
2. Use peerDependencyRules instead of ignoring all warnings
3. Document suppressed warnings explaining why they're safe
4. Keep peer deps ranges wide in libraries:
{
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0"
}
}5. Test with different peer versions if you support multiple majors
<!-- Source references:
- https://pnpm.io/package_json#pnpmpeerdependencyrules
- https://pnpm.io/npmrc#auto-install-peers
-->
Related skills
Forks & variants (3)
Pnpm has 3 known copies in the catalog totaling 53 installs. They canonicalize to this original listing.
- sanity-io - 35 installs
- next-safe-action - 10 installs
- jetbrains - 8 installs
How it compares
Use the pnpm skill for cache-tuned GitHub Actions YAML; consult package docs for local workspace linking beyond CI.
FAQ
What is the workspace protocol in pnpm?
The workspace protocol allows dependencies to reference other packages in the same monorepo using workspace:* instead of version numbers.
How does pnpm save disk space?
pnpm uses a content-addressable store and hard links to deduplicate packages across all projects on a machine.
Is Pnpm safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.