pnpm
pnpm is a JavaScript package manager that installs dependencies faster and with far less disk space than npm or classic Yarn, while being stricter about what code can import. Instead of copying every package into every project, pnpm keeps one copy of each package version in a global content-addressable store and hard-links files into projects. Instead of a flat node_modules where any package can reach any other, it builds a symlinked layout in which each package sees only the dependencies it declares.
That strictness catches real bugs — code importing packages it never listed — and pnpm's workspace features make it one of the most popular choices for monorepos. It is a drop-in replacement for most npm workflows: the same package.json, the same registry, a different lockfile (pnpm-lock.yaml).
TL;DR
- One global store of package files, hard-linked into projects: fast installs and minimal disk use.
- Strict
node_modules: packages can only import their declared dependencies, preventing phantom dependencies. - Workspaces via
pnpm-workspace.yaml, theworkspace:protocol, and powerful--filterselection. - Catalogs define shared dependency versions once for a whole monorepo.
- pnpm 10 does not run dependency install scripts unless approved, and can delay brand-new releases with
minimumReleaseAge. - In CI, install with a frozen lockfile and cache the store.
Quick Example
A monorepo workspace with shared versions and supply-chain controls:
An app depending on a local package and a catalog version:
Day-to-day commands:
Core Concepts
The Content-Addressable Store
Every file of every package version is stored once in a global store, addressed by its content hash. Installing a package into a project creates hard links (or copy-on-write clones) to those files, so ten projects using the same React version share one copy on disk. Updating one version of a large package only stores the files that changed.
Strict, Symlinked node_modules
A phantom dependency is a package your code imports without declaring it — it works only because something else happened to install it. It breaks when that other dependency changes. pnpm surfaces these immediately.
Workspaces
pnpm-workspace.yaml lists the packages in a monorepo. The workspace: protocol links local packages instead of downloading them, and is replaced with real version ranges on publish. Filters select packages by name, directory, dependency relationships (... for dependents or dependencies), or changes since a git ref, which makes CI run only what a change affects. See Monorepos.
Catalogs and Overrides
Catalogs declare dependency versions once in the workspace file; packages refer to them with catalog:. Upgrading React across thirty packages becomes a one-line change. Overrides force a specific version of a transitive dependency, for example to patch a vulnerable package deep in the tree.
Security Controls
pnpm 10 changed the default for lifecycle scripts: dependencies' preinstall and postinstall scripts do not run unless the package is allowed (onlyBuiltDependencies, or interactively with pnpm approve-builds). Since many supply-chain attacks execute through install scripts, this closes a major hole. minimumReleaseAge delays adopting newly published versions, giving the ecosystem time to detect compromised releases. See Software Supply Chain Security.
pnpm Compared With Other Package Managers
See Package Managers and Bun.
Using pnpm in CI
Pin the pnpm version with the packageManager field in the root package.json so every developer and CI job uses the same version.
Best Practices
Commit the Lockfile and Freeze It in CI
pnpm-lock.yaml records exact versions and integrity hashes. CI should fail if it is out of date rather than silently resolving new versions.
Fix Phantom Dependencies, Do Not Hide Them
When a strict import fails, add the missing dependency to the package that imports it. Hoisting everything back to the top level gives up pnpm's main safety benefit.
Approve Build Scripts Deliberately
Allow install scripts only for packages that genuinely need them, such as native modules, and review the list when it changes.
Use Catalogs for Shared Versions
Keep frameworks, TypeScript, and test tools on one version across the monorepo with catalogs instead of dozens of independent ranges.
Common Mistakes
Mixing Package Managers
Hoisting Everything for Compatibility
Relying on Implicit Workspace Resolution
FAQ
Why is pnpm faster than npm?
It reuses files from a global store via hard links instead of copying them, resolves and fetches in parallel, and skips work for packages already in the store. Projects that share dependencies install especially quickly.
Is pnpm compatible with npm packages?
Yes. It uses the same registry and package.json format. Occasionally, packages that rely on undeclared dependencies break under pnpm's strict layout; the fix is usually declaring the dependency or a targeted configuration.
What is a phantom dependency?
A package your code imports without listing it in package.json, which only works because another dependency installed it. pnpm's strict layout makes such imports fail, exposing the missing declaration.
Does pnpm run postinstall scripts?
Since pnpm 10, dependency lifecycle scripts do not run by default. You allow specific packages through onlyBuiltDependencies or by approving them with pnpm approve-builds.
Should I use pnpm or Bun?
pnpm is a mature, highly compatible package manager with strong monorepo features. Bun is a whole runtime and toolkit with a very fast installer. Many teams use pnpm with Node.js for compatibility and use Bun where its runtime fits.
Related Topics
- npm vs pnpm vs Yarn — Compare install modes, lock enforcement, workspaces, and migration evidence.
- Package Managers — Comparing package managers across ecosystems
- Node.js — The runtime pnpm installs packages for
- Monorepos — Workspaces, filtering, and shared tooling
- Software Supply Chain Security — Protecting dependency installs
- Bun — An alternative runtime and package manager
- uv — A similarly fast, cache-based package manager for Python