pnpm 12.0 ships as a Rust rewrite, not a migration

pnpm 12.0 ships as a Rust rewrite, not a migration

pnpm 12.0 is out as a stable release, and it is a full rewrite of pnpm in Rust. The pnpm team is explicit that this is not a migration: the commands, flags, settings and lockfile format from pnpm 11 all carry over unchanged, and the documentation covers both versions side by side. The npm "latest" tag still points at the pnpm 11 line, so installing pnpm 12 requires the next-12 tag explicitly, for example running "pnpm self-update next-12". Homebrew, winget, Scoop and Chocolatey do not yet offer it.

Several behaviors change under the hood. Git dependency specifiers (github:owner/repo, owner/repo, git+https, git+ssh) for repositories on GitHub, GitLab and Bitbucket now resolve through the host's canonical HTTPS URL rather than choosing a transport, and the lockfile no longer records an SSH URL for those hosts; reaching a private repository over SSH now requires configuring git's own URL rewriting on the machine. An unrecognized setting in pnpm-workspace.yaml, previously ignored silently, is now reported, with a suggested correction when the key looks like a typo; it hard-fails the command when the project pins a pnpm version that the running pnpm satisfies, and is only a warning otherwise. Lockfiles of cyclic dependency graphs are now canonicalized: cycle members are ordered by package id and broken at the same edge regardless of where the install walks in, so the lockfile becomes a pure function of the dependency graph and repeated installs produce byte-identical output. On large, cycle-heavy workspaces this makes peer resolution 2 to 3 times faster and cuts memory use by about 25%, per the release notes, which give no benchmark methodology or workspace size. On Linux, packageImportMethod: auto now tries a hardlink before a reflink, which roughly halves the time an install spends materializing node_modules from a warm store on btrfs (ext4 was already hardlink-first, and macOS keeps clone-first via APFS). Under engineStrict, an install now fails when an incompatible package is reached through a regular dependency edge even if that whole subtree hangs off an optionalDependencies entry; pnpm 11 only warned in that case.

pnpm 12 also adds project-aware global bins: a globally installed Node.js, Deno or Bun now follows the version the current project pins rather than always running the global one, controlled by a new globalShims setting. pnpm can now provision other package managers itself: npm, Yarn Classic, Yarn Berry, Yarn 6 and Bun, each fetched through trusted registries and, for npm-published ones, verified against npm's signature before running. This covers installing git-hosted dependencies that need a different package manager, running one-off commands through pnx, and linking a project-pinned package manager with pnpm shim add. Registries can now serve a "revision", a replacement artifact for an already-published version, for example a security rebuild, addressed by its SHA-512 digest and recorded as one extra lockfile line, without changing the version number. pnpm init now pins the latest released pnpm instead of the version that ran the command, falling back to the running version if the lookup cannot complete. pnpm stage approve can now approve several staged packages in one batch with a single one-time password. A new audit.ignorePrune setting lets pnpm audit --fix drop ignored GHSA entries that no longer appear in the audit report. Global commands that modify the global installation now refuse to run under sudo. An opt-in, proof-of-concept remote side-effects cache lets installs reuse a dependency's build output across machines via signed, organization-scoped artifacts, restoring for now only on Linux/glibc x64 and arm64, with every cache failure falling back to a local build.

On the fixes side, the built-in compatibility database no longer adds dependencies detected purely by static analysis of published packages, since those entries sometimes pulled in packages a project only imported for its types and broke builds; the release notes cite @typescript-eslint/types pulling in a TypeScript 7 dependency that made ESLint fail. When no directory above a project accepts a hard link, such as an AI agent sandbox or a container with only the project mounted writable, the default store is now created inside the project's node_modules instead of the home directory. The hooks.filterLog pnpmfile hook is deprecated in favor of the loglevel setting.

Key facts

  • pnpm 12.0 is a stable, full Rust rewrite that its team says is deliberately not a migration: pnpm 11's commands, flags, settings and lockfile format all carry over.
  • Lockfiles of cyclic dependency graphs are now canonicalized to be byte-identical across repeated installs, making peer resolution 2 to 3 times faster and cutting memory use by about 25% on large, cycle-heavy workspaces.
  • On Linux, packageImportMethod: auto now tries a hardlink before a reflink, roughly halving node_modules materialization time from a warm store on btrfs.
  • pnpm can now provision other package managers itself: npm, Yarn Classic, Yarn Berry, Yarn 6 and Bun, each fetched through trusted registries and, for npm-published ones, verified against npm's signature before running.
  • Installing pnpm 12 requires the next-12 tag (npm's "latest" still points to pnpm 11), and Homebrew, winget, Scoop and Chocolatey do not offer it yet.

Why it matters

pnpm 12 is a full Rust rewrite that deliberately preserves pnpm 11's interface, so the payoff is speed and correctness rather than a new tool to learn. The canonicalized lockfile format for cyclic dependency graphs is the most consequential change: byte-identical lockfiles across repeated installs and reordered importers is a genuine correctness fix, useful for CI diff noise and cache-key stability, on top of the 2 to 3 times faster and roughly 25% less memory peer resolution on large, cycle-heavy workspaces.

Who it affects

JavaScript and TypeScript teams already on pnpm, most directly workspaces with heavy peer-dependency cycles and large monorepos that will see the biggest install-time and memory wins. Linux developers on btrfs get faster node_modules materialization from packageImportMethod: auto. Anyone depending on git-hosted packages over SSH now needs to configure git's own URL rewriting for private repositories. CI pipelines relying on engineStrict now see installs fail that pnpm 11 only warned about.

How to use it

Because npm's "latest" tag still points at pnpm 11, upgrading means running pnpm self-update next-12 or otherwise installing from the next-12 tag; Homebrew, winget, Scoop and Chocolatey do not carry it yet. Existing lockfiles keep working unchanged under --frozen-lockfile, and the first real re-resolution re-keys only the walk-order-dependent peer variants of cyclic packages. Teams with private SSH git dependencies, unrecognized pnpm-workspace.yaml settings, or engineStrict enabled should check the release notes' breaking-changes section before upgrading a CI pipeline that pins a pnpm version.

How solid is it

This is the pnpm project's own release announcement, so the technical detail (settings names, error codes, lockfile fields) is first-party and specific throughout. The performance figures (2 to 3 times faster peer resolution and about 25% less memory) are the release notes' own claims; no benchmark methodology, hardware, or workspace size is disclosed alongside them, so they read as the maintainers' figures rather than independently verified ones.

Risks and caveats

Several changes can break existing setups. Git dependency specifiers now resolve through the host's canonical HTTPS URL and drop SSH URLs from the lockfile, which breaks private-repository access until git's own URL rewriting is configured. An unrecognized pnpm-workspace.yaml setting now hard-fails the command outright when the project pins a satisfied pnpm version. engineStrict now fails installs that pnpm 11 merely warned about. The filterLog pnpmfile hook stops working and needs replacing with loglevel. The new remote side-effects cache is explicitly a proof of concept, restoring only on Linux/glibc x64 and arm64 for now.