Skip to content

pnpm

pnpm (Performant npm) is a package manager for Node.js that saves disk space by storing all packages in a global content-addressable store and using hard links to project node_modules. This means packages are never duplicated on disk, installs are faster, and all projects share the same cached packages.

Installation

# npm (recommended for bootstrapping)
npm install -g pnpm

# Corepack (built into Node.js 16.9+)
corepack enable
corepack prepare pnpm@latest --activate

# macOS (Homebrew)
brew install pnpm

# Windows (Scoop)
scoop install nodejs-lts
corepack enable

# Linux standalone install
curl -fsSL https://get.pnpm.io/install.sh | sh -

# Upgrade pnpm
pnpm self-update

# Check version
pnpm --version

# Set Node.js version to use (pnpm manages Node versions too)
pnpm env use --global lts
pnpm env use --global 20
# Enable corepack (ships with Node.js)
corepack enable pnpm

# Pin pnpm version per project in package.json
# Add to package.json:
# "packageManager": "pnpm@9.1.0"

Configuration

pnpm uses .npmrc for configuration and pnpm-workspace.yaml for monorepos:

# .npmrc
# Store location (default: ~/.pnpm-store)
store-dir=~/.pnpm-store

# Use shamefully-hoist to fix compatibility with tools that expect flat node_modules
# (only use if a package explicitly requires it)
shamefully-hoist=false

# Strict mode — fail on peer dependency issues
strict-peer-dependencies=true

# Auto-install peer dependencies
auto-install-peers=true

# Save exact versions (no ^ or ~)
save-exact=false

# Custom registry
registry=https://registry.npmjs.org/

# Scoped registry
@myorg:registry=https://npm.myorg.com/

# Link workspace packages by default
link-workspace-packages=true

# Prefer frozen lockfile in CI (set via env var instead)
# PNPM_FROZEN_LOCKFILE=true

Monorepo Workspace Config

# pnpm-workspace.yaml
packages:
  - "packages/*"
  - "apps/*"
  - "!**/__tests__/**"

package.json Overrides

{
  "pnpm": {
    "overrides": {
      "lodash": "^4.17.21",
      "minimist@<1.2.6": "^1.2.6"
    },
    "peerDependencyRules": {
      "ignoreMissing": ["react"],
      "allowAny": ["typescript"]
    },
    "packageExtensions": {
      "some-lib@*": {
        "peerDependencies": {
          "react": "*"
        }
      }
    }
  }
}

Core Commands

CommandDescription
pnpm installInstall all dependencies
pnpm install --frozen-lockfileInstall without updating lockfile (CI)
pnpm add packageAdd a dependency
pnpm add -D packageAdd a dev dependency
pnpm add -g packageInstall globally
pnpm add -O packageAdd as optional dependency
pnpm add package@2.0.0Add specific version
pnpm remove packageRemove a dependency
pnpm updateUpdate packages per semver range
pnpm update --latestUpdate to latest versions
pnpm update packageUpdate a specific package
pnpm outdatedCheck for outdated packages
pnpm run scriptRun a script from package.json
pnpm run -r scriptRun script in all workspace packages
pnpm exec commandRun a command from node_modules/.bin
pnpm dlx packageExecute package without installing (npx equivalent)
pnpm listList installed packages
pnpm list --depth 0List top-level packages only
pnpm why packageShow why a package is installed
pnpm store statusCheck if store is up to date
pnpm store pruneRemove unused packages from store
pnpm auditAudit packages for vulnerabilities
pnpm patch packagePatch a dependency
pnpm deploy ./deploy-dirCreate a production-ready copy
pnpm initInitialize a new package.json
pnpm publishPublish package to npm registry

Advanced Usage

Workspace Filtering

# Run build in a specific package
pnpm --filter my-app build

# Run tests in all packages matching pattern
pnpm --filter "*-lib" test

# Run in all packages that have changed since main
pnpm --filter "[main]" build

# Run in packages that depend on my-lib (dependents)
pnpm --filter "...my-lib" build

# Run in my-lib and all its dependencies
pnpm --filter "my-lib..." build

# Combine filters
pnpm --filter "my-app" --filter "my-lib" build

# Run in all workspace packages
pnpm -r build
pnpm -r --parallel build  # run in parallel

Dependency Patching

# Start patching a dependency
pnpm patch lodash@4.17.21

# After editing files in the temp directory printed above:
pnpm patch-commit /tmp/patch-dir

# This creates patches/lodash@4.17.21.patch and adds to package.json:
# "pnpm": { "patchedDependencies": { "lodash@4.17.21": "patches/lodash@4.17.21.patch" } }

Deploy Command (for Monorepos)

# Create a self-contained deployment directory
# Only includes the app and its workspace dependencies
pnpm --filter my-app deploy --prod ./deploy

# The deploy directory has its own node_modules with no symlinks
# Safe to copy to production server or Docker image

Catalog (pnpm 9+)

# pnpm-workspace.yaml — define shared version catalog
packages:
  - "packages/*"

catalog:
  react: "^18.3.0"
  typescript: "^5.4.0"
  vitest: "^1.6.0"
// packages/my-app/package.json
{
  "dependencies": {
    "react": "catalog:"
  },
  "devDependencies": {
    "typescript": "catalog:",
    "vitest": "catalog:"
  }
}

Content-Addressable Store

# View store location
pnpm store path

# Check store integrity
pnpm store status

# Remove unused packages (safe to run periodically)
pnpm store prune

# Add packages to store without linking (warm up cache)
pnpm fetch

Setting Up pnpm in Docker

FROM node:20-alpine

# Enable corepack and install pnpm
RUN corepack enable && corepack prepare pnpm@latest --activate

WORKDIR /app

# Copy lockfile and manifests first for layer caching
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
COPY packages/my-app/package.json ./packages/my-app/
COPY packages/my-lib/package.json ./packages/my-lib/

# Install deps (uses frozen lockfile)
RUN pnpm install --frozen-lockfile

# Copy source
COPY . .
RUN pnpm --filter my-app build

Common Workflows

Setting Up a Monorepo

# Create root
mkdir my-monorepo && cd my-monorepo
pnpm init

# Create workspace config
cat > pnpm-workspace.yaml << 'EOF'
packages:
  - "packages/*"
  - "apps/*"
EOF

# Create packages
mkdir -p packages/ui apps/web
cd packages/ui && pnpm init && cd ../..
cd apps/web && pnpm init && cd ../..

# Add shared root devDependencies
pnpm add -D -w typescript vitest

# Add workspace package as dependency
pnpm --filter web add @myorg/ui --workspace

Adding Overrides to Fix Security Vulnerabilities

# Check what's vulnerable
pnpm audit

# Override the vulnerable version
# Add to package.json "pnpm.overrides":
# "vulnerable-pkg@<2.0.0": "^2.0.0"

pnpm install  # applies override
pnpm audit    # verify fix

Publishing Monorepo Packages

# Build all packages
pnpm -r build

# Publish all public packages
pnpm -r publish --access public

# Dry run to preview
pnpm -r publish --dry-run

Tips and Best Practices

Commit pnpm-lock.yaml — Always commit the lockfile. Use pnpm install --frozen-lockfile in CI to ensure deterministic installs. Set CI=true to enable this automatically.

Use pnpm dlx instead of npx — pnpm dlx is the pnpm equivalent of npx and is faster because it leverages the pnpm store for caching.

Avoid shamefully-hoist — The default strict node_modules structure catches phantom dependencies (packages you use but didn’t declare). Only enable hoisting if a specific package explicitly requires it.

Use pnpm why to audit dependencies — Before adding a package, run pnpm why package to understand if it’s already a transitive dependency you could use directly.

Prune the store periodically — Run pnpm store prune monthly to remove packages that no longer appear in any lockfile. This keeps disk usage in check.

Pin pnpm version with packageManager — Add "packageManager": "pnpm@9.x.x" to your root package.json. With corepack enabled, this ensures all contributors use the same version.

{
  "packageManager": "pnpm@9.1.0+sha256...."
}

Use the catalog for version consistency — In pnpm 9+, the workspace catalog ensures every package in a monorepo uses exactly the same version of shared tools like TypeScript, Vitest, and React — with a single place to update them.

Run scripts in parallel carefully — pnpm -r --parallel run build runs all builds simultaneously regardless of dependencies. Use --stream to see output in real time. For topology-aware ordering, use Turborepo or Nx on top of pnpm.