PageSourceSearch

https://pnpm.io/blog/whats-different-in-pnpm-12

html pnpm.io collected 2026-10-01 07:29:40 UTC 16,350 bytes, 133 lines download raw bytes

1<!doctype html>
2<html lang="en">
3  <head><!-- first-paint -->
3<script>(function(){var t;try{t=localStorage.getItem("theme")}catch(e){}if(t!=='light'&&t!=='dark')t=window.matchMedia&&matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light';document.documentElement.setAttribute('data-theme',t)})()</script>
3<link rel="preconnect" href="https://fonts.googleapis.com" /><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" /><style>body{font-family:var(--typography-font-family,system-ui,sans-serif);background:var(--colors-surface-background,#fff);color:var(--colors-text-primary,#212121);margin:0}.docs-prerendered{box-sizing:border-box;overflow-wrap:break-word;max-width:860px;margin:0 auto;padding:88px 24px 48px;line-height:1.6}.docs-prerendered h1{margin:0 0 24px;font-size:36px;line-height:1.2}.docs-prerendered h2{margin:48px 0 16px;font-size:30px;line-height:1.2}.docs-prerendered h3{margin:32px 0 12px;font-size:24px;line-height:1.2}.docs-prerendered a{color:var(--colors-text-link,#f69220)}.docs-prerendered .hash-link{display:none}.docs-prerendered img{max-width:100%;height:auto}.docs-prerendered pre{background:var(--colors-surface-tertiary,#f8f9fa);border-radius:8px;padding:16px;overflow-x:auto}.docs-prerendered code{font-size:.9em}.docs-prerendered table{border-collapse:collapse;display:block;overflow-x:auto}.docs-prerendered th,.docs-prerendered td{border:1px solid var(--colors-border-default,#e0e0e0);padding:8px 12px}.docs-prerendered article+article{margin-top:48px}</style>
4    <meta charset="UTF-8" />
5    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6    <title>What's different in pnpm 12 | pnpm</title>
7    <meta name="description" content="pnpm 12 is a rewrite of pnpm in Rust, and it is stable. Upgrading should not feel like a migration. Apart from the differences below, it keeps the commands, flags, settings, and lockfile format of pnpm 11, and the documentation applies to both versions." />
8    <meta name="theme-color" content="#f69220" />
9    <link rel="icon" href="/img/favicon.png" />
10    <link rel="alternate" type="application/rss+xml" title="pnpm Blog RSS Feed" href="/blog/rss.xml" />
11    <link rel="alternate" type="application/atom+xml" title="pnpm Blog Atom Feed" href="/blog/atom.xml" />
12
13    <meta property="og:type" content="website" />
14    <meta property="og:site_name" content="pnpm" />
15    <meta property="og:title" content="What's different in pnpm 12 | pnpm" />
16    <meta property="og:description" content="pnpm 12 is a rewrite of pnpm in Rust, and it is stable. Upgrading should not feel like a migration. Apart from the differences below, it keeps the commands, flags, settings, and lockfile format of pnpm 11, and the documentation applies to both versions." />
17    <meta property="og:image" content="https://pnpm.io/img/ogimage.png" />
18
19    <meta name="twitter:card" content="summary_large_image" />
20    <meta name="twitter:site" content="@pnpmjs" />
21    <meta name="twitter:title" content="What's different in pnpm 12 | pnpm" />
22    <meta name="twitter:description" content="pnpm 12 is a rewrite of pnpm in Rust, and it is stable. Upgrading should not feel like a migration. Apart from the differences below, it keeps the commands, flags, settings, and lockfile format of pnpm 11, and the documentation applies to both versions." />
23    <meta name="twitter:image" content="https://pnpm.io/img/ogimage.png" />
24
25    
25<script type="module" crossorigin src="/assets/index-CjpiBuHV.js"></script>
25
26    <link rel="stylesheet" crossorigin href="/assets/index-gKrm_uHA.css">
27  <link rel="canonical" href="https://pnpm.io/blog/whats-different-in-pnpm-12" />
28    <meta property="og:url" content="https://pnpm.io/blog/whats-different-in-pnpm-12" />
29    <link rel="alternate" hreflang="en" href="https://pnpm.io/blog/whats-different-in-pnpm-12" />
30    <link rel="alternate" hreflang="it" href="https://pnpm.io/it/blog/whats-different-in-pnpm-12" />
31    <link rel="alternate" hreflang="zh" href="https://pnpm.io/zh/blog/whats-different-in-pnpm-12" />
32    <link rel="alternate" hreflang="ja" href="https://pnpm.io/ja/blog/whats-different-in-pnpm-12" />
33    <link rel="alternate" hreflang="ko" href="https://pnpm.io/ko/blog/whats-different-in-pnpm-12" />
34    <link rel="alternate" hreflang="pt" href="https://pnpm.io/pt/blog/whats-different-in-pnpm-12" />
35    <link rel="alternate" hreflang="zh-TW" href="https://pnpm.io/zh-TW/blog/whats-different-in-pnpm-12" />
36    <link rel="alternate" hreflang="ru" href="https://pnpm.io/ru/blog/whats-different-in-pnpm-12" />
37    <link rel="alternate" hreflang="uk" href="https://pnpm.io/uk/blog/whats-different-in-pnpm-12" />
38    <link rel="alternate" hreflang="fr" href="https://pnpm.io/fr/blog/whats-different-in-pnpm-12" />
39    <link rel="alternate" hreflang="tr" href="https://pnpm.io/tr/blog/whats-different-in-pnpm-12" />
40    <link rel="alternate" hreflang="es" href="https://pnpm.io/es/blog/whats-different-in-pnpm-12" />
41    <link rel="alternate" hreflang="id" href="https://pnpm.io/id/blog/whats-different-in-pnpm-12" />
42    <link rel="alternate" hreflang="x-default" href="https://pnpm.io/blog/whats-different-in-pnpm-12" />
43    <meta name="docsearch:language" content="en" />
44    <meta name="docsearch:docusaurus_tag" content="default" />
45  </head>
46  <body>
47    <div id="root"><main class="docs-prerendered"><article><h1>What's different in pnpm 12</h1><p>pnpm 12 is a rewrite of pnpm in Rust, and it is stable. Upgrading should not feel like a migration. Apart from the differences below, it keeps the commands, flags, settings, and lockfile format of pnpm 11, and <a href="/motivation">the documentation</a> applies to both versions.</p>
48<p>Eight things differ. Six of them change a result, and two reject command-line syntax that pnpm 11 accepted. This post collects them in one place.</p>
49<h2 id="project-aware-global-bins">Project-aware global bins<a class="hash-link" href="#project-aware-global-bins" aria-label="Direct link to Project-aware global bins">#</a></h2>
50<p>A globally installed <code>node</code>, <code>deno</code>, or <code>bun</code> now runs the version the current project pins.</p>
51<p>A project pins a runtime through <a href="/package_json#devenginesruntime"><code>devEngines.runtime</code></a> or by installing the runtime as a dependency. Run <code>node</code> inside that project and you get the pinned version. Run it outside any project and you get the global one. You no longer need a separate version manager for this.</p>
52<p>The setting that controls this is <a href="/settings/other#globalshims"><code>globalShims</code></a>, and the full description is in <a href="/global-packages#project-aware-global-bins">Project-aware global bins</a>.</p>
53<h2 id="git-dependency-resolution">Git dependency resolution<a class="hash-link" href="#git-dependency-resolution" aria-label="Direct link to Git dependency resolution">#</a></h2>
54<p>For repositories on GitHub, GitLab, and Bitbucket, a specifier now only says which repository you want, not how to reach it. All of these name the same dependency and resolve the same way:</p>
55<pre><code>kevva/is-positive
56github:kevva/is-positive
57git+https://github.com/kevva/is-positive.git
58git+ssh://[email protected]/kevva/is-positive.git
59</code></pre>
60<p>Each resolves through the host's HTTPS URL, and pnpm never records an SSH URL for those hosts. How a machine reaches the host is that machine's Git configuration, not something the project decides. A lockfile written on a laptop with SSH keys installs on a CI runner without them.</p>
61<p>pnpm 11 tried transports in turn and could record <code>[email protected]:owner/repo.git</code>, which then failed for everyone without a key for that host. If an older lockfile has such an entry, run <code>pnpm update &#x3C;package></code> once to re-resolve it. pnpm does not rewrite the entry on its own. The lockfile is the record of what to install, and pnpm leaves it alone until you ask.</p>
62<p>To reach private repositories over SSH, tell Git to rewrite the URL:</p>
63<pre><code class="hljs language-sh">git config --global url.<span class="hljs-string">"[email protected]:"</span>.insteadOf https://github.com/
64</code></pre>
65<p>pnpm shells out to <code>git</code>, so the rewrite applies to every Git operation pnpm runs. Unknown hosts and URLs with embedded credentials are covered in <a href="/package-sources#how-git-dependencies-are-resolved">How Git dependencies are resolved</a>.</p>
66<h2 id="naming-a-package-manager">
66Naming a package manager<a class="hash-link" href="#naming-a-package-manager" aria-label="Direct link to Naming a package manager">#</a></h2>
67<p>pnpm 12 can install the other package managers: npm, Yarn Classic, Yarn Berry, Yarn 6 (<code>yarnpkg/zpm</code>), and Bun. As a result, naming one of them means the tool, not the npm package that shares its name.</p>
68<p>Those npm packages are not the tool. <code>yarn</code> on npm stops at Classic. Yarn 4 is published as <code>@yarnpkg/cli-dist</code>. Yarn 6 is not on npm at all. <code>node</code> and <code>deno</code> on npm are wrappers that download a build. So <code>pnx yarn@4 install</code> used to fail with a missing version, and <code>pnpm add -g yarn</code> gave you Yarn 1.</p>
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104<div class="table-wrapper"><table><thead><tr><th></th><th>pnpm 11</th><th>pnpm 12</th></tr></thead><tbody><tr><td><code>pnpm add yarn</code></td><td>installs the npm package <code>yarn</code></td><td>records the project's package manager in <code>packageManager</code> / <code>devEngines.packageManager</code></td></tr><tr><td><code>pnpm add -g yarn</code></td><td>installs Yarn Classic</td><td>installs the current Yarn line</td></tr><tr><td><code>pnpm add -g node</code> / <code>pnpm add -g deno</code></td><td>installs a wrapper package</td><td>installs that Node.js or Deno release</td></tr><tr><td><code>pnx node@22</code> / <code>pnx deno</code></td><td>runs the wrapper package</td><td>runs that release</td></tr><tr><td>a globally installed package manager</td><td>always the global copy</td><td>defers to a project's pin where there is one</td></tr></tbody></table></div>
105<p>A specifier that points at a specific package still installs that package. <code>pnpm add yarn@npm:[email protected]</code> gives you the npm package, and <code>pnx yarn@yarnpkg/berry</code> runs Yarn from its repository.</p>
106<p>Two more things follow. A git-hosted dependency is built with the package manager its own repository asks for, so a repository that uses Yarn installs on a machine that has only pnpm. And <a href="/cli/shim"><code>pnpm shim add yarn</code></a> links a <code>yarn</code> command that runs whatever version the current project pins. pnpm never creates such a shim as a side effect of <code>pnpm setup</code> or an install, because the shim shadows the rest of your <code>PATH</code>.</p>
107<p>The full description is in <a href="/package-managers">Other package managers</a>.</p>
108<h2 id="lockfiles-of-cyclic-dependency-graphs">Lockfiles of cyclic dependency graphs<a class="hash-link" href="#lockfiles-of-cyclic-dependency-graphs" aria-label="Direct link to Lockfiles of cyclic dependency graphs">#</a></h2>
109<p>pnpm 12 breaks dependency cycles at a fixed place. It orders the packages in a cycle by package ID and always cuts the same edge, no matter where the install entered the cycle. pnpm 11 cut wherever it happened to walk in.</p>
110<p>The lockfile now depends only on the dependency graph. Reorder the <code>packages</code> globs in <code>pnpm-workspace.yaml</code>, reorder entries in <code>package.json</code>, or install twice, and you get the same bytes. In pnpm 11 a project with cycles could get a different lockfile each time. Workspaces with many cycles also resolve peers 2–3× faster and use about 25% less memory. Their lockfiles shrink too, because a package inside a cycle no longer gets a separate peer variant for every path that reaches it.</p>
111<p>Existing lockfiles keep working. <code>--frozen-lockfile</code> installs them as they are, and an install that doesn't re-resolve leaves them untouched. The first install that does re-resolve rewrites the peer variants of cyclic packages, so expect a one-time lockfile diff. Details in <a href="/how-peers-are-resolved#cyclic-dependencies">How peers are resolved</a>.</p>
112<h2 id="packageimportmethod-auto-hardlinks-first-on-linux"><code>packageImportMethod: auto</code> hardlinks first on Linux<a class="hash-link" href="#packageimportmethod-auto-hardlinks-first-on-linux" aria-label="Direct link to packageImportMethod: auto hardlinks first on Linux">#</a></h2>
113<p>On Linux, the default <a href="/settings/node-modules#packageimportmethod"><code>packageImportMethod: auto</code></a> now tries a hardlink before a reflink. Hardlinks are cheaper to create, and on btrfs this roughly halves the time an install spends materializing <code>node_modules</code> from a warm store.</p>
114<p>Cloning is still the second choice. A store that refuses a hardlink gets a clone, and a store that refuses that too gets a copy. <code>packageImportMethod: clone</code> still asks for a clone outright. Pick that if you edit files inside <code>node_modules</code>, because a hardlinked file is the store's file. Nothing changes on ext4, which never supported cloning, so <code>auto</code> already hardlinked there. macOS keeps clone-first because APFS <code>clonefile</code> is fast.</p>
115<p>pnpm 11 keeps clone-first on Linux. Changing what the default writes to disk is not something for a point release.</p>
116<h2 id="enginestrict-and-optional-subtrees"><code>engineStrict</code>
116 and optional subtrees<a class="hash-link" href="#enginestrict-and-optional-subtrees" aria-label="Direct link to engineStrict and optional subtrees">#</a></h2>
117<p>With <a href="/settings/cli#enginestrict"><code>engineStrict</code></a> on, pnpm 12 fails the install when a package that is being installed depends, through regular <code>dependencies</code>, on a package with an incompatible engine. It no longer matters that the whole subtree sits under an <code>optionalDependencies</code> entry. pnpm 11 installs the package and prints an install-check warning.</p>
118<p>What gets skipped has not changed. A package reachable only through optional edges, or through a package that was itself skipped, is still skipped in both versions (<a href="https://github.com/pnpm/pnpm/issues/13286">#13286</a>).</p>
119<h2 id="pnpm-install---resolution-only-is-gone"><code>pnpm install --resolution-only</code> is gone<a class="hash-link" href="#pnpm-install---resolution-only-is-gone" aria-label="Direct link to pnpm install --resolution-only is gone">#</a></h2>
120<p>pnpm 12 does not implement this flag and rejects it:</p>
121<pre><code>error: unexpected argument '--resolution-only' found
122</code></pre>
123<p>The flag existed to print peer dependency issues. <a href="/cli/peers#check"><code>pnpm peers check</code></a> does that instead. It reads the issues from the lockfile, so it needs neither a re-resolution nor an install:</p>
124<pre><code class="hljs language-sh">pnpm peers check
125</code></pre>
126<p>If a CI script calls <code>--resolution-only</code>, replace it before switching to pnpm 12.</p>
127<h2 id="explicit-frozen-lockfile-values">Explicit frozen-lockfile values<a class="hash-link" href="#explicit-frozen-lockfile-values" aria-label="Direct link to Explicit frozen-lockfile values">#</a></h2>
128<p><code>pnpm install --frozen-lockfile false</code> is no longer supported. Use <code>pnpm install --no-frozen-lockfile</code> to disable frozen-lockfile mode. To enable it, use <code>pnpm install --frozen-lockfile</code> without a separate <code>true</code> argument (<a href="https://github.com/pnpm/pnpm/issues/14741">pnpm/pnpm#14741</a>).</p>
129<h2 id="trying-it">Trying it<a class="hash-link" href="#trying-it" aria-label="Direct link to Trying it">#</a></h2>
130<p><code>latest</code> on npm still points at the pnpm 11 line, so install pnpm 12 from the <code>latest-12</code> tag. Homebrew, winget, Scoop, and Chocolatey don't offer it yet. <a href="/installation">Installing pnpm 12</a> lists the ways to install it.</p>
131<p>Please <a href="https://github.com/pnpm/pnpm/issues">report any issues</a> you run into.</p></article></main></div>
132  </body>
133</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.