· 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 Ethan · 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
eslintorprettierin 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
-
Bump
packageManagerinpackage.json:{ "packageManager": "[email protected]" } -
Run
pnpm installand read the output. pnpm will list any packages whose lifecycle scripts were silenced. -
Run
pnpm approve-buildsto interactively decide which packages need build scripts. The choices are written into theallowBuildsmap inpnpm-workspace.yaml. -
Fix hoisting if your workspace runs linters from the root. Either add them to
public-hoist-patternin.npmrcor 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. -
Update CI. If your pipeline sets
PNPM_HOMEor uses the pnpm setup action, bump the version there too. GitHub Actions example:- uses: pnpm/action-setup@v4 with: version: 10 -
Check for issue #9082 if you’re running
shared-workspace-lockfile=false. As of the time of writing,onlyBuiltDependenciesdoesn’t take effect correctly in that configuration. The workaround per the pnpm maintainer: ensurepnpm.onlyBuiltDependenciesis set in the rootpackage.json, not in a nested workspace package’spackage.json. Beyond that, there is no clean workaround yet forshared-workspace-lockfile=falsesetups — 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
onlyBuiltDependencieslist.
Wait if:
- Your install pipeline relies on a long list of third-party
postinstallscripts 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=falsewith 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.