Package Manager

pnpm

pnpm is a fast, disk-efficient package manager that uses a content-addressable store and strict symlinked node_modules. It is the go-to choice for monorepos.

intermediate13 min readUpdated Sep 15, 2026
pnpm-workspace.yaml
yaml
// pnpm-workspace.yaml
packages:
  - "apps/*"
  - "packages/*"

// packages/ui/package.json
{
  "name": "@repo/ui",
  "version": "0.0.0",
  "private": true,
  "main": "./src/index.ts"
}
Store
Content-addressable
node_modules
Symlinked and strict
Disk usage
Shared across projects
Workspaces
Built in
Lockfile
pnpm-lock.yaml
Best for
Monorepos

Why it matters

Why teams switch to pnpm

Saves disk space

Every package version is stored once in a global store and hard-linked into projects, so ten projects do not mean ten copies.

Fast installs

A content-addressable store plus parallel operations make installs significantly faster than a fresh download each time.

Strict by default

A package can only import what it declares, which prevents phantom dependencies from working by accident.

The big picture

The three ideas behind pnpm

A shared global store, symlinked node_modules that expose only declared dependencies, and first-class workspaces.

The store

Storage

A single global cache of package contents, hard-linked into every project that needs them.

node_modules

Resolution

A symlinked layout that mirrors the real dependency graph and enforces declared dependencies.

Workspaces

Monorepos

First-class support for multi-package repositories with the workspace protocol.

pnpm at a glance

The core of pnpm

pnpm add

Add dependencies, with -D for development tools and -w for the workspace root.

Strict layout

Only declared dependencies are importable, catching missing entries early.

Workspaces

Define package globs in pnpm-workspace.yaml.

workspace protocol

Reference sibling packages with workspace:* instead of file paths.

Catalogs

Centralise dependency versions across packages.

Frozen lockfile

CI installs exactly what the lockfile specifies.

A short history

From a fast npm clone to the monorepo default

  1. 2017

    pnpm released

    A package manager built around a content-addressable store for speed and disk efficiency.

    17
  2. 2019

    Workspaces mature

    First-class monorepo support makes pnpm popular in larger repositories.

    19
  3. 2021

    Growing adoption

    Frameworks and libraries begin documenting pnpm alongside npm.

    21
  4. 2023

    Catalogs and more

    Shared version catalogs simplify dependency management across packages.

    23
  5. Today

    The monorepo default

    A common choice for monorepos and a fast drop-in for many npm workflows.

    Today

The complete guide

pnpm: Everything you need to know

What is pnpm?

pnpm is a package manager that stores every version of every package once in a global content-addressable store and hard-links it into each project that needs it. The result is dramatically less disk usage and much faster installs, especially across many projects or a monorepo.

It is also strict. Instead of flattening every dependency into one node_modules, pnpm uses symlinks that mirror the real dependency graph. A package can only import what it actually declares, so accidental reliance on a transitive dependency fails immediately instead of working locally and breaking in production.

The store and node_modules

The store lives in a global directory and holds the contents of every package version you have ever installed. When a project needs a package, pnpm hard-links it from the store rather than copying it.

The node_modules layout then uses symlinks:

  • node_modules/.pnpm holds the real packages.
  • node_modules/<name> links to the version your project declared.
  • Each package’s own node_modules links only to its declared dependencies.

This is why pnpm catches phantom dependencies: if your code imports a package you forgot to add to package.json, it fails, because the package is not linked at the top level. npm’s flat layout often lets that mistake pass unnoticed.

Commands

The commands are close to npm’s, which makes migration easy.

pnpm install          # install from the lockfile
pnpm add zod          # add a dependency
pnpm add -D vitest    # add a dev dependency
pnpm remove zod       # remove a dependency
pnpm run build        # run a script
pnpm dlx create-vite  # run a package without installing

pnpm install uses the store, so repeated installs are fast. pnpm-lock.yaml plays the same role as package-lock.json and should be committed.

Workspaces

Workspaces are pnpm’s standout feature. You declare the package locations in pnpm-workspace.yaml.

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

Then a workspace can depend on a sibling using the workspace protocol instead of a relative file path.

{
  "name": "@repo/web",
  "dependencies": {
    "@repo/ui": "workspace:*"
  }
}

pnpm links the local package, and workspace:* is replaced with the real version when publishing. This keeps internal dependencies explicit and avoids brittle file:../.. paths.

Catalogs and version consistency

In a large monorepo it is easy for different packages to depend on different versions of the same library. Catalogs centralise that decision.

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

catalog:
  react: ^19.0.0
  typescript: ^5.6.0
{
  "dependencies": {
    "react": "catalog:"
  }
}

Every package that uses catalog: resolves to the version defined once at the root, which makes upgrades a single edit and prevents drift.

Filtering and running tasks

pnpm can target a subset of a workspace, which is essential in a monorepo.

# run tests only in packages that changed since main
pnpm --filter "...[origin/main]" test

# run a script in one package
pnpm --filter @repo/web dev

# run a script in every package
pnpm -r build

Filters support package names, directory globs and dependency relationships, so you can run a command only where it is relevant. For caching and orchestration across packages, pair pnpm with Turborepo, which is designed for exactly this setup.

CI and reproducibility

Use a frozen lockfile in CI so the install fails if the lockfile and manifests disagree.

pnpm install --frozen-lockfile
pnpm run build

This is the pnpm equivalent of npm ci and is what guarantees a reproducible build. Because installs are fast and the store can be cached in CI, pnpm also tends to reduce pipeline time.

pnpm compared with npm

  • Disk and speed: pnpm shares packages through a store and links them; npm copies a flattened tree per project.
  • Strictness: pnpm exposes only declared dependencies; npm’s flat layout allows phantom imports.
  • Workspaces: both support them, but pnpm’s workspace protocol and filtering are more ergonomic for large monorepos.
  • Compatibility: both read package.json and support the same registry, so switching is usually a matter of deleting node_modules and the old lockfile.

Choose npm for simplicity and ubiquity, and pnpm when disk, speed or monorepo ergonomics matter. See the npm guide for the default workflow.

Best practices

  • Commit pnpm-lock.yaml.
  • Use pnpm install --frozen-lockfile in CI.
  • Use workspace:* for internal dependencies.
  • Centralise shared versions with catalogs.
  • Take advantage of strictness: add missing dependencies instead of disabling it.
  • Use --filter to run tasks only where they matter.
  • Pair pnpm with a task runner for caching in large repos.

Common mistakes

  • Adding dependencies at the wrong workspace level with -w.
  • Using file: paths instead of the workspace protocol.
  • Ignoring “not declared in package.json” errors instead of fixing the manifest.
  • Forgetting --frozen-lockfile in CI and getting non-reproducible installs.
  • Letting each package pin its own version of a shared library.
  • Mixing package managers in one repository and creating conflicting lockfiles.

Where to go next

pnpm is the efficient choice for modern JavaScript projects, especially monorepos. Compare it with npm, add Turborepo for task caching, and understand the Node.js runtime underneath. Then try it on an existing project by removing node_modules and letting pnpm rebuild the tree from the store.

Referencing a workspace package

The workspace protocol links local packages explicitly. Relative file paths are brittle and break when folders move.

Prefer
{
  "dependencies": {
    "@repo/ui": "workspace:*"
  }
}
Avoid
{
  "dependencies": {
    "@repo/ui": "file:../../packages/ui"
  }
}

Installing in CI

A frozen lockfile installs exactly what was committed and fails on a mismatch, which keeps builds reproducible.

Prefer
pnpm install --frozen-lockfile
pnpm run build
Avoid
# may update the lockfile
# and install different
# versions than local
pnpm install
pnpm run build

FAQ

Frequently asked questions

Keep learning

Related topics from the roadmap.

$ start learning

Ready to start learning pnpm?

Our interactive tutorial walks you through pnpm step by step — with quizzes and real code you can run in the browser.