Highlights
Major
- Node.js 22+ required — support for Node 18, 19, 20, and 21 is dropped, pnpm itself is now pure ESM, and the standalone exe requires glibc 2.27.
- Supply-chain protection on by default —
minimumReleaseAgedefaults to 1 day (newly published packages are not resolved for 24h) andblockExoticSubdepsdefaults totrue. allowBuildsreplaces the old build-dependency settings —onlyBuiltDependencies,onlyBuiltDependenciesFile,neverBuiltDependencies,ignoredBuiltDependencies, andignoreDepScriptshave been removed.- Global installs are isolated and use the global virtual store by default — each
pnpm add -ggets its own directory with its ownpackage.json,node_modules, and lockfile. - New SQLite-backed store index (store v11) with bundled manifests and hex digests, reducing filesystem syscalls and speeding up installation.
- Native publish flow —
pnpm publish,login,logout,view,deprecate,unpublish,dist-tag, andversionno longer delegate to the npm CLI, and the remaining npm passthrough commands now throw "not implemented". .npmrcis auth/registry only — all other settings must live inpnpm-workspace.yamlor the new globalconfig.yaml, and environment variables use thepnpm_config_*prefix.
Minor
- New commands:
pnpm ci,pnpm sbom,pnpm clean,pnpm peers check, andpnpm runtime set, pluspn/pnxshort aliases. - ESM pnpmfiles via
.pnpmfile.mjs, which takes priority over.pnpmfile.cjswhen present. pnpm audit --fix=updatefixes vulnerabilities by updating packages in the lockfile instead of adding overrides.- Faster HTTP and I/O — undici with Happy Eyeballs, direct-to-CAS writes, skipped staging directory, pre-allocated tarball downloads, and an NDJSON metadata cache.
Major Changes
Requirements
- pnpm is now distributed as pure ESM.
- Dropped support for Node.js v18, 19, 20, and 21.
- The standalone exe version of pnpm requires at least glibc 2.27.
Security & Build Defaults
-
Changed default values:
optimisticRepeatInstallis nowtrue,verifyDepsBeforeRunis nowinstall,minimumReleaseAgeis now1440(1 day), andminimumReleaseAgeStrictisfalse. Newly published packages will not be resolved until they are at least 1 day old. This protects against supply chain attacks by giving the community time to detect and remove compromised versions. To opt out, setminimumReleaseAge: 0inpnpm-workspace.yaml#11158. -
strictDepBuildsistrueby default. -
blockExoticSubdepsistrueby default. -
Removed deprecated build dependency settings:
onlyBuiltDependencies,onlyBuiltDependenciesFile,neverBuiltDependencies,ignoredBuiltDependencies, andignoreDepScripts#11220.Use the
allowBuildssetting instead. It is a map where keys are package name patterns and values are booleans:truemeans the package is allowed to run build scriptsfalsemeans the package is explicitly denied from running build scripts
Same as before, by default, none of the packages in the dependencies are allowed to run scripts. If a package has postinstall scripts and it isn't declared in
allowBuilds, an error is printed.Before:
onlyBuiltDependencies: - electron onlyBuiltDependenciesFile: "allowed-builds.json" neverBuiltDependencies: - core-js ignoredBuiltDependencies: - esbuild
After:
allowBuilds: electron: true core-js: false esbuild: false
-
Removed
allowNonAppliedPatchesin favor ofallowUnusedPatches. -
Removed
ignorePatchFailures; patch application failures now throw an error.
Store
- Runtime dependencies are always linked from the global virtual store #10233.
- Optimized index file format to store the hash algorithm once per file instead of repeating it for every file entry. Each file entry now stores only the hex digest instead of the full integrity string (
<algo>-<digest>). Using hex format improves performance since file paths in the content-addressable store use hex representation, eliminating base64-to-hex conversion during path lookups. - Store version bumped to v11.
- The bundled manifest (name, version, bin, engines, scripts, etc.) is now stored directly in the package index file, eliminating the need to read
package.jsonfrom the content-addressable store during resolution and installation. This reduces I/O and speeds up repeat installs #10473. - The package index in the content-addressable store is now backed by SQLite. Instead of individual JSON files under
$STORE/index/, package metadata is stored in a single SQLite database at$STORE/index.dbwith MessagePack-encoded values. This reduces filesystem syscall overhead, improves space efficiency for small metadata entries, and enables concurrent access via SQLite's WAL mode. Packages missing from the new index are re-fetched on demand #10500 #10826.
Global Packages
-
Global installs (
pnpm add -g pkg) andpnxnow use the global virtual store by default. Packages are stored at{storeDir}/linksinstead of per-project.pnpmdirectories. This can be disabled by settingenableGlobalVirtualStore: false#10694. -
Isolated global packages. Each globally installed package (or group of packages installed together) now gets its own isolated installation directory with its own
package.json,node_modules/, and lockfile. This prevents global packages from interfering with each other through peer dependency conflicts, hoisting changes, or version resolution shifts.Key changes:
pnpm add -g <pkg>creates an isolated installation in{pnpmHomeDir}/global/v11/{hash}/pnpm remove -g <pkg>removes the entire installation group containing the packagepnpm update -g [pkg]re-installs packages in new isolated directoriespnpm list -gscans isolated directories to show all installed global packagespnpm install -g(no args) is no longer supported; usepnpm add -g <pkg>instead
-
Globally installed binaries are now stored in a
binsubdirectory ofPNPM_HOMEinstead of directly inPNPM_HOME. This prevents internal directories likeglobal/andstore/from polluting shell autocompletion whenPNPM_HOMEis on PATH #10986. After upgrading, runpnpm setupto update your shell configuration. -
Breaking changes to
pnpm link:pnpm link <pkg-name>no longer resolves packages from the global store. Only relative or absolute paths are accepted. For example, usepnpm link ./fooinstead ofpnpm link foo.pnpm link --globalis removed. Usepnpm add -g .to register a local package's bins globally.pnpm link(no arguments) is removed. Usepnpm link <dir>with an explicit path instead.
Configuration
-
pnpm no longer reads all settings from
.npmrc. Only auth and registry settings are read from.npmrcfiles. All other settings (likehoistPattern,nodeLinker,shamefullyHoist, etc.) must be configured inpnpm-workspace.yamlor the global~/.config/pnpm/config.yaml#11189. -
Network settings (
httpProxy,httpsProxy,noProxy,localAddress,strictSsl,gitShallowHosts) are now written toconfig.yaml(global) orpnpm-workspace.yaml(local) instead of.npmrc/auth.ini. They are still readable from.npmrcfor easier migration from the npm CLI #11209.pnpm no longer reads
npm_config_*environment variables. Usepnpm_config_*environment variables instead (e.g.,pnpm_config_registryinstead ofnpm_config_registry).pnpm no longer reads the npm global config at
$PREFIX/etc/npmrc.pnpm loginwrites auth tokens to~/.config/pnpm/auth.ini.New
registriessetting inpnpm-workspace.yaml:registries: default: https://registry.npmjs.org/ "@my-org": https://private.example.com/ "@internal": https://nexus.corp.com/
Auth tokens in
~/.npmrcstill work — pnpm continues to read~/.npmrcas a fallback for registry authentication. The newnpmrcAuthFilesetting can be used to point to a different file instead of~/.npmrc. -
Replace workspace project specific
.npmrcwithpackageConfigsinpnpm-workspace.yaml.A workspace manifest with
packageConfigslooks something like this:# File: pnpm-workspace.yaml packages: - "packages/project-1" - "packages/project-2" packageConfigs: "project-1": saveExact: true "project-2": savePrefix: "~"
Or this:
# File: pnpm-workspace.yaml packages: - "packages/project-1" - "packages/project-2" packageConfigs: - match: ["project-1", "project-2"] modulesDir: "node_modules" saveExact: true
-
pnpm no longer reads settings from the
pnpmfield ofpackage.json. Settings should be defined inpnpm-workspace.yaml#10086. -
pnpm config get(without--json) no longer prints INI formatted text. Instead, it prints JSON for objects and arrays, and raw strings for strings, numbers, booleans, and nulls.pnpm config get --jsonstill prints all types of values as JSON, as before. -
pnpm config get <array>now prints a JSON array. -
pnpm config listnow prints a JSON object instead of INI formatted text. -
pnpm config listandpnpm config get(without argument) now hide auth-related settings. -
pnpm config listandpnpm config get(without argument) now show top-level keys as camelCase. Exception: keys that start with@or//are preserved (their cases don't change). -
pnpm config getandpnpm config listno longer load non-camelCase options from the workspace manifest (pnpm-workspace.yaml).
Removed Commands & npm Passthrough
-
pnpm no longer falls back to the npm CLI. Commands that were previously passed through to npm (
access,bugs,docs,edit,find,home,issues,owner,ping,prefix,profile,pkg,repo,search,set-script,star,stars,team,token,unstar,whoami,xmas) and their aliases (s,se) now throw a "not implemented" error, with a suggestion to use the npm CLI directly #10642. Other previously passed-through commands —view(info,show,v),login(adduser),logout,deprecate,unpublish,dist-tag, andversion— have been reimplemented natively in pnpm (see New Commands below). -
pnpm publishnow works without thenpmCLI.The One-time Password feature now reads from
PNPM_CONFIG_OTPinstead ofNPM_CONFIG_OTP:export PNPM_CONFIG_OTP='<your OTP here>' pnpm publish --no-git-checks
If the registry requests OTP and the user has not provided it via the
PNPM_CONFIG_OTPenvironment variable or the--otpflag, pnpm will prompt the user directly for an OTP code.If the registry requests web-based authentication, pnpm will print a scannable QR code along with the URL.
Since the new
pnpm publishno longer callsnpm publish, some undocumented features may have been unknowingly dropped. If you rely on a feature that is now gone, please open an issue at https://github.com/pnpm/pnpm/issues. In the meantime, you can usepnpm pack && npm publish *.tgzas a workaround. -
Removed the
pnpm servercommand #10463. -
Removed support for the
useNodeVersionandexecutionEnv.nodeVersionfields.devEngines.runtimeandengines.runtimeshould be used instead #10373. -
Removed support for
hooks.fetchers. We now have a new API for custom fetchers and resolvers via thefetchersfield ofpnpmfile.
Lifecycle Scripts
- pnpm no longer populates
npm_config_*environment variables from the pnpm config during lifecycle scripts. Only well-knownnpm_*env vars are now set, matching Yarn's behavior #11116.
CLI Output
- Cleaner output for script execution: pnpm now prints
$ commandinstead of> pkg@version stage path\n> command, and shows project name and path only when running in a different directory. The$ commandline is printed to stderr to keep stdout clean for piping #11132. - During install, instead of rendering the full peer dependency issues tree, pnpm now suggests running
pnpm peers checkto view the issues #11133.
Lockfile
- Simplified
patchedDependencieslockfile format fromRecord<string, { path: string, hash: string }>toRecord<string, string>(selector to hash). Existing lockfiles with the old format are automatically migrated #10911.
Other
-
The default value of the
typefield in thepackage.jsonfile of the project initialized bypnpm initcommand has been changed tomodule. -
Added support for lowercase options in
pnpm add:-d,-p,-o,-e#9197.When using the
pnpm addcommand only:-pis now an alias for--save-prodinstead of--parseable-dis now an alias for--save-devinstead of--loglevel=info
-
The root workspace project is no longer excluded when it is explicitly selected via a filter #10465.
Minor Changes
New Commands
- Added native
pnpm view(info,show,v) command for viewing package metadata from the registry #11064. - Added
pnpm login(andpnpm adduseralias) command for authenticating with npm registries. Supports web-based login with QR code as well as classic username/password login #11094. - Added
pnpm logoutcommand for logging out of npm registries. Revokes the authentication token on the registry and removes it from the local auth config file #11213. - Added native
pnpm deprecateandpnpm undeprecatecommands for setting and removing deprecation messages on package versions without delegating to the npm CLI #11120. - Added native
pnpm unpublishcommand. Supports unpublishing specific versions, version ranges via semver, and entire packages with--force#11128. - Added native
pnpm dist-tagcommand (ls,add,rmsubcommands) #11218. - Added
pnpm sbomcommand for generating Software Bill of Materials in CycloneDX 1.7 and SPDX 2.3 JSON formats #9088. - Added
pnpm cleancommand that safely removesnode_modulesdirectories from all workspace projects #10707. Use--lockfileto also removepnpm-lock.yamlfiles. - Added a new command
pnpm runtime set <runtime name> <runtime version spec> [-g]for installing runtimes. Deprecatedpnpm env usein favor of the new command. - Added the ability to fix vulnerabilities by updating packages in the lockfile instead of adding overrides. Use
pnpm audit --fix=update#10341. - Added
pnpm cicommand for clean installs #6100. The command runspnpm cleanfollowed bypnpm install --frozen-lockfile. Designed for CI/CD environments where reproducible builds are critical. Aliases:pnpm clean-install,pnpm ic,pnpm install-clean#11003. - Added
pnpm peers checkcommand that checks for unmet and missing peer dependency issues by reading the lockfile #7087. - Implemented the
versioncommand natively in pnpm to support workspaces andworkspace:protocols correctly. The new command allows bumping package versions (major, minor, patch, etc.) with full workspace support and git integration #10879.
Configuration
-
Added support for a global YAML config file named
config.yaml.Configuration is now split into two categories:
- Registry and auth settings, which can be stored in INI files such as the global
rcfile and local.npmrc. - pnpm-specific settings, which can only be loaded from YAML files such as the global
config.yamland localpnpm-workspace.yaml.
- Registry and auth settings, which can be stored in INI files such as the global
-
Added support for loading environment variables whose names start with
pnpm_config_into config. These environment variables override settings frompnpm-workspace.yamlbut not CLI arguments. -
Added support for reading
allowBuildsfrompnpm-workspace.yamlin the global package directory for global installs. -
Added support for
pnpm config get globalconfigto retrieve the global config file path #9977. -
Added a new setting
virtualStoreOnlythat populates the virtual store without creating importer symlinks, hoisting, bin links, or running lifecycle scripts. This is useful for pre-populating a store (e.g., in Nix builds) without creating unnecessary project-level artifacts.pnpm fetchnow uses this mode internally #10840. -
Added support for specifying the pnpm version via
devEngines.packageManagerinpackage.json. Unlike thepackageManagerfield, this supports version ranges. The resolved version is stored inpnpm-lock.yamland reused if it still satisfies the range #10932. -
Added a new
dedupePeerssetting that reduces peer dependency duplication. When enabled, peer dependency suffixes use version-only identifiers (name@version) instead of full dep paths, eliminating nested suffixes like(foo@1.0.0(bar@2.0.0)). This dramatically reduces the number of package instances in projects with many recursive peer dependencies #11070. -
Config dependencies are now installed into the global virtual store (
{storeDir}/links/) and symlinked intonode_modules/.pnpm-config/. This allows config dependencies to be shared across projects that use the same store, avoiding redundant fetches and imports #10910. Config dependency and package manager integrity info is now stored inpnpm-lock.yamlinstead of inlined inpnpm-workspace.yaml: the workspace manifest contains only clean version specifiers forconfigDependencies, while the resolved versions, integrity hashes, and tarball URLs are recorded in the lockfile as a separate YAML document. The env lockfile section also storespackageManagerDependenciesresolved during version switching and self-update. Projects using the old inline-hash format are automatically migrated on install #10912 #10964. -
Added
nodeDownloadMirrorssetting to configure custom Node.js download mirrors inpnpm-workspace.yaml. This replaces thenode-mirror:<channel>.npmrcsetting, which is no longer read #11194:nodeDownloadMirrors: release: https://my-mirror.example.com/download/release/
Store
- When the global virtual store is enabled, packages that are not allowed to build (and don't transitively depend on packages that are) now get hashes that don't include the engine name (platform, architecture, Node.js major version). This means ~95% of packages in the GVS survive Node.js upgrades and architecture changes without re-import #10837.
Hooks & Pnpmfiles
- Added support for pnpmfiles written in ESM, using the
.mjsextension. When.pnpmfile.mjsexists, it takes priority over.pnpmfile.cjsand only one is loaded #9730.
CLI & Other
- The built-in
clean,setup,deploy, andrebuildcommands now prefer user scripts over built-in commands. When a project'spackage.jsonhas a script with the same name,pnpmexecutes the script instead of the built-in command. Addedpurgeas an alias for the built-incleancommand, which always runs the built-in regardless of scripts #11118. - Added
-Fas a short alias for the--filteroption. - Added support for hidden scripts. Scripts starting with
.are hidden and cannot be run directly viapnpm run. They can only be called from other scripts. Hidden scripts are also omitted from thepnpm runlisting #11041. pnpm approve-buildsnow accepts positional arguments for approving or denying packages without the interactive prompt. Prefix a package name with!to deny it. Only mentioned packages are affected; the rest are left untouched #11030.- During install, packages with ignored builds that are not yet listed in
allowBuildsare automatically added topnpm-workspace.yamlwith a placeholder value, so users can manually set them totrueorfalse#11030. - Added
pnandpnxshort aliases forpnpmandpnpx(pnpm dlx) #11052. pnpm store prunenow displays the total size of removed files #11047.pnpm audit --fixnow adds the minimum patched version for each advisory tominimumReleaseAgeExcludeinpnpm-workspace.yaml, so the security fix can be installed without waiting forminimumReleaseAge#11216.- pnpm now warns when
optimisticRepeatInstallskipsshouldRefreshResolutionhooks #10995.
Performance
- Replaced
node-fetchwith nativeundicifor HTTP requests throughout pnpm #10537. - Eliminated redundant internal linking during GVS warm reinstall when no packages were added #11073.
- Eliminated the staging directory when importing packages into
node_modules, avoiding the overhead of creating a temp dir and renaming per package #11088. - CAS files are now written directly to their final content-addressed path instead of to a temp file and renamed. This eliminates ~30k rename syscalls per cold install #11087.
- Optimized hot-path string operations in the content-addressable store and increased
gunzipSyncchunk size for fewer buffer allocations during tarball decompression #11086. - Improved HTTP performance with Happy Eyeballs (dual-stack), better keep-alive settings, and an optimized global dispatcher. Tarball downloads with known size now pre-allocate memory to avoid double-copy overhead #11151.
- Adopted
If-Modified-Sincefor conditional metadata fetches, avoiding re-downloading unchanged registry metadata #11161. - Switched to abbreviated metadata when checking
minimumReleaseAge, reducing the amount of data fetched from the registry #11160. - Switched the metadata cache to NDJSON format, improving read/write performance #11188.
Platinum Sponsors
|
|
Gold Sponsors
|
|
|
|
|
|
|
|
|
|
|