· pnpm / nodejs / package-manager

pnpm 10 review — what changed and is it worth upgrading?

pnpm 10 blocks all dependency lifecycle scripts by default — a real supply-chain security win. Here is what changed and who should upgrade now.

By · Updated May 31, 2026

943 words · 5 min read

If you’re on a new project or you want stricter dependency isolation, upgrade to pnpm 10 now. If your install depends on third-party postinstall scripts or you run linters from a monorepo root, plan a half-day to audit and allowlist before you bump.

Who this is for

Node.js developers who manage dependencies with pnpm — solo, team, or monorepo — and are evaluating whether v10 is worth the migration cost. If you’re still on pnpm 8 or below, read the v9 changelog first. If you’re still deciding between package managers, see pnpm vs npm or pnpm vs Yarn.

What changed in pnpm 10

Lifecycle scripts are blocked by default

This is the one change that will break most projects on first install.

pnpm 10 no longer executes lifecycle scripts (postinstall, prepare, preinstall) for any dependency unless you explicitly opt in. The release notes say it plainly:

“Lifecycle scripts of dependencies are not executed during installation by default! This is a breaking change aimed at increasing security.”

The motivation is real. postinstall is the most common vector for supply-chain attacks: a compromised package can run arbitrary code on every developer machine and every CI runner that installs it. pnpm 10 closes that by default.

To re-enable scripts for packages that legitimately need them — native modules, anything that compiles C++ or downloads a binary at install time — add an allowlist to package.json:

{
  "pnpm": {
    "onlyBuiltDependencies": ["esbuild", "sharp", "@img/sharp-darwin-arm64"]
  }
}

If you’re not sure which packages need postinstall, run pnpm approve-builds (introduced in v10.1.0):

pnpm approve-builds

The interactive CLI shows each package that would run a build script and lets you approve or deny it one by one. As of v10.32, pnpm approve-builds --all approves everything in one shot — useful during migration when you want to restore original behavior temporarily while you audit.

public-hoist-pattern is now empty

In pnpm 9 and earlier, eslint and prettier packages were hoisted to node_modules/.bin at the workspace root automatically. That’s gone.

“Nothing is hoisted by default. Packages containing eslint or prettier in their name are no longer hoisted.”

Monorepos that run eslint from the root via a script like "lint": "eslint ." will fail with Cannot find module 'eslint' or eslint: command not found. Fix: install eslint at the root explicitly, or configure public-hoist-pattern in .npmrc:

public-hoist-pattern[]=*eslint*
public-hoist-pattern[]=*prettier*

Store version bumped to v10

The content-addressable store switched to SHA256 hashing throughout and uses a new index/ directory structure. On first install after upgrading pnpm, some packages may re-download as pnpm reconciles the old store format against the new one. This is a one-time cost. The old store in ~/.pnpm-store/v3 (v9) is not cleaned up automatically — run pnpm store prune when you’re satisfied the upgrade is stable.

Performance

pnpm.io’s benchmark page (checked May 31, 2026) shows a JS-baseline install with cache + lockfile + node_modules at ~449ms. The Rust-engine variant of pnpm (pnpm 🦀) clocks the same scenario at 56ms — roughly 8× faster.

One caveat: the official docs don’t publish a side-by-side v9 vs v10 JS-baseline comparison. The numbers above describe current v10 throughput, not a delta over v9.

Upgrade guide

  1. Bump packageManager in package.json:

    { "packageManager": "[email protected]" }
  2. Run pnpm install and read the output. pnpm will list any packages whose lifecycle scripts were silenced.

  3. Run pnpm approve-builds to interactively decide which packages need build scripts. The choices are written into the allowBuilds map in pnpm-workspace.yaml.

  4. Fix hoisting if your workspace runs linters from the root. Either add them to public-hoist-pattern in .npmrc or install the tooling explicitly at the workspace root. For a clean monorepo setup with pnpm and Turborepo, see How to Set Up a pnpm + Turborepo Monorepo from Scratch.

  5. Update CI. If your pipeline sets PNPM_HOME or uses the pnpm setup action, bump the version there too. GitHub Actions example:

    - uses: pnpm/action-setup@v4
      with:
        version: 10
  6. Check for issue #9082 if you’re running shared-workspace-lockfile=false. As of the time of writing, onlyBuiltDependencies doesn’t take effect correctly in that configuration. The workaround per the pnpm maintainer: ensure pnpm.onlyBuiltDependencies is set in the root package.json, not in a nested workspace package’s package.json. Beyond that, there is no clean workaround yet for shared-workspace-lockfile=false setups — track the upstream issue for a fix.

There is no official migration page at pnpm.io/migration/v10 — the de facto guide is the v10.0.0 GitHub release notes. There is no automated codemod (a codemod exists for v10→v11, but not v9→v10).

Verdict

Upgrade now if:

  • You’re starting a new project.
  • You want stricter isolation from supply-chain attacks — the lifecycle-script block is a meaningful security improvement.
  • You’re already managing an explicit onlyBuiltDependencies list.

Wait if:

  • Your install pipeline relies on a long list of third-party postinstall scripts you haven’t audited.
  • You’re running linters from a monorepo root and don’t have time to sort out hoisting.
  • You use shared-workspace-lockfile=false with complex workspace configurations until issue #9082 is resolved.

Caveats

  • We did not test on Windows. pnpm has historically had rough edges on Windows with symlinks; assume the upgrade path is similar but verify.
  • No v9 vs v10 JS-baseline performance comparison was available from the pnpm team at publication time. The 449ms number is v10 throughput, not a measured improvement over v9.
  • Affiliate links: none. pnpm is open-source.

References