Passa al contenuto principale

pnpm 11.0

· 11 minuti di lettura
Zoltan Kochan
Lead maintainer of pnpm

pnpm 11 is here! This release tightens the security defaults introduced throughout the v10 cycle, drops the npm CLI fallback for publishing in favor of a native implementation, replaces the JSON-per-package store index with a single SQLite database, and isolates global installs so they no longer interfere with each other.

It also requires Node.js 22 or newer — pnpm itself is now pure ESM.

Upgrading from v10? See the Migrating from v10 to v11 guide. Most config changes are mechanical and can be applied by the pnpm-v10-to-v11 codemod.

Highlights

  • Node.js 22+ required. Support for Node 18, 19, 20, and 21 is dropped. The standalone executable requires glibc 2.27 or newer.
  • Supply-chain protection on by default. minimumReleaseAge defaults to 1440 (1 day) and blockExoticSubdeps defaults to true.
  • allowBuilds replaces the legacy build-dependency settings. onlyBuiltDependencies, onlyBuiltDependenciesFile, neverBuiltDependencies, ignoredBuiltDependencies, and ignoreDepScripts are gone.
  • Global installs are isolated and use the global virtual store by default. Each pnpm add -g gets its own directory with its own package.json, node_modules, and lockfile.
  • New SQLite-backed store index (store v11), with bundled manifests and hex digests for fewer syscalls and faster installs.
  • Native publish flow. pnpm publish, login, logout, view, deprecate, unpublish, dist-tag, and version no longer delegate to the npm CLI.
  • .npmrc is auth/registry only. Other settings must live in pnpm-workspace.yaml or the new global config.yaml. Environment variables use the pnpm_config_* prefix.

Breaking changes

Requirements

  • Drops Node.js 18, 19, 20, and 21.
  • pnpm is now distributed as pure ESM.
  • The standalone exe requires glibc 2.27+.

Security & build defaults

Several defaults have flipped to safer values:

SettingNew default
minimumReleaseAge1440 (1 day)
minimumReleaseAgeStrictfalse
blockExoticSubdepstrue
strictDepBuildstrue
optimisticRepeatInstalltrue
verifyDepsBeforeRuninstall

Newly published packages won't be resolved until they're at least 1 day old. To opt out, set minimumReleaseAge: 0 in pnpm-workspace.yaml.

allowBuilds replaces the old build settings

onlyBuiltDependencies, onlyBuiltDependenciesFile, neverBuiltDependencies, ignoredBuiltDependencies, and ignoreDepScripts have all been removed. Use allowBuilds instead — a map from package name patterns to booleans:

Before:

onlyBuiltDependencies:
- electron
onlyBuiltDependenciesFile: "allowed-builds.json"
neverBuiltDependencies:
- core-js
ignoredBuiltDependencies:
- esbuild

After:

allowBuilds:
electron: true
core-js: false
esbuild: false

Also removed: allowNonAppliedPatches (use allowUnusedPatches) and ignorePatchFailures (patch failures now throw).

.npmrc is auth/registry only

pnpm no longer reads non-auth settings from .npmrc. Configuration is split into two categories:

  • Registry and auth settings — INI files (.npmrc, the global rc file, ~/.config/pnpm/auth.ini).
  • pnpm-specific settings — YAML files (pnpm-workspace.yaml, the new global ~/.config/pnpm/config.yaml).

Other related changes:

  • pnpm no longer reads npm_config_* environment variables. Use pnpm_config_* instead (e.g. pnpm_config_registry).

  • pnpm no longer reads the pnpm field in package.json.

  • pnpm no longer reads npm's global config at $PREFIX/etc/npmrc.

  • Network settings (httpProxy, httpsProxy, noProxy, localAddress, strictSsl, gitShallowHosts) are now written to config.yaml / pnpm-workspace.yaml (still readable from .npmrc to ease migration).

  • A new registries setting in pnpm-workspace.yaml replaces @scope:registry= lines:

    registries:
    default: https://registry.npmjs.org/
    "@my-org": https://private.example.com/
    "@internal": https://nexus.corp.com/
  • Per-project .npmrc is replaced by packageConfigs in pnpm-workspace.yaml:

    packages:
    - "packages/project-1"
    - "packages/project-2"
    packageConfigs:
    "project-1":
    saveExact: true
    "project-2":
    savePrefix: "~"

Native publish flow, no more npm CLI fallback

Commands previously implemented by passing through to the npm CLI have either been reimplemented natively or removed.

Reimplemented: publish, view (info, show, v), login (adduser), logout, deprecate, unpublish, dist-tag, version, search, star/unstar/stars, whoami, ping, docs/home.

Removed (now throw "not implemented"): access, bugs, edit, issues, owner, prefix, profile, pkg, repo, set-script, team, token, xmas.

A few notes on the new native pnpm publish:

  • The OTP environment variable is now PNPM_CONFIG_OTP (was NPM_CONFIG_OTP).
  • If the registry asks for OTP and none is provided, pnpm prompts for it interactively.
  • Web-based authentication shows a scannable QR code and URL.

pnpm audit uses the bulk advisories endpoint

The legacy /-/npm/v1/security/audits{,/quick} endpoints have been retired by the registry. pnpm audit now calls /-/npm/v1/security/advisories/bulk, which doesn't return CVE identifiers — so CVE-based filtering has been replaced with GHSA-based filtering:

  • auditConfig.ignoreCvesauditConfig.ignoreGhsas
  • pnpm audit --ignore <id> and --ignore-unfixable read and write GHSAs

To migrate, replace each CVE-YYYY-NNNNN entry under ignoreCves with the matching GHSA-xxxx-xxxx-xxxx (visible in the More info column of pnpm audit) and move it to ignoreGhsas.

Isolated, global-virtual-store global installs

pnpm add -g <pkg> and pnx now use the global virtual store, and each install gets its own isolated directory at {pnpmHomeDir}/global/v11/{hash}/ with its own package.json, node_modules/, and lockfile. This stops global packages from interfering with each other through peer-dependency conflicts, hoisting changes, or version drift.

  • pnpm remove -g <pkg> removes the entire installation group containing the package.
  • pnpm update -g [pkg] re-installs into a new isolated directory.
  • pnpm list -g scans isolated directories.
  • pnpm install -g (no args) is no longer supported — use pnpm add -g <pkg>.

Globally installed binaries now live in a bin subdirectory of PNPM_HOME (rather than directly in PNPM_HOME), so internal directories like global/ and store/ don't pollute shell autocompletion. Run pnpm setup after upgrading to update your shell configuration.

pnpm link has been tightened too: only relative or absolute paths are accepted, --global is removed (use pnpm add -g .), and pnpm link with no arguments is removed.

Other removals

  • pnpm server.

  • useNodeVersion and executionEnv.nodeVersion — use devEngines.runtime / engines.runtime.

  • hooks.fetchers — replaced by the new fetchers field in pnpmfile.

  • managePackageManagerVersions, packageManagerStrict, and packageManagerStrictVersion. These all derived the onFail behavior of the legacy packageManager field; the new pmOnFail setting subsumes them:

    RemovedReplace with
    managePackageManagerVersions: truepmOnFail: download (default)
    managePackageManagerVersions: falsepmOnFail: ignore
    packageManagerStrict: falsepmOnFail: warn
    packageManagerStrictVersion: truepmOnFail: error
    COREPACK_ENABLE_STRICT=0pmOnFail: warn

New commands

ComandoWhat it does
pnpm ciRuns pnpm clean followed by pnpm install --frozen-lockfile. Aliases: clean-install, ic, install-clean.
pnpm cleanRemoves node_modules from all workspace projects. --lockfile also removes pnpm-lock.yaml.
pnpm sbomGenerates a Software Bill of Materials in CycloneDX 1.7 or SPDX 2.3 JSON.
pnpm peers checkReports unmet/missing peers from the lockfile.
pnpm runtime setInstalls a runtime; deprecates pnpm env use.
pnpm docs / homeOpens the package homepage.
pnpm pingPings the registry.
pnpm withRuns pnpm at a specific (or current) version for a single invocation, bypassing packageManager pins.
pnpm pack-appPacks a CommonJS entry into a standalone executable for one or more target platforms via Node.js SEA.

Plus the natively reimplemented commands listed under "Native publish flow" above, and short aliases pn for pnpm and pnx for pnpx/pnpm dlx.

pnpm audit --fix=update

Fix vulnerabilities by updating packages in the lockfile instead of adding overrides. For more granular control, the new --interactive / -i flag lets you select which advisories to fix:

pnpm audit --fix=update --interactive

pnpm audit --fix also adds the minimum patched version for each advisory to minimumReleaseAgeExclude in pnpm-workspace.yaml, so security fixes can be installed without waiting for minimumReleaseAge.

ESM pnpmfiles

Pnpmfiles can now be written in ESM, using the .mjs extension. When .pnpmfile.mjs exists, it takes priority over .pnpmfile.cjs, and only one is loaded.

Store v11

The store has been rebuilt around two ideas: read less, do fewer syscalls.

  • The package index is now a single SQLite database at $STORE/index.db (with MessagePack values, WAL mode for concurrent access), instead of millions of JSON files under $STORE/index/. Packages missing from the new index are re-fetched on demand.
  • The bundled manifest (name, version, bin, engines, scripts, etc.) is stored directly in the package index, eliminating the need to read package.json from the CAS during resolution and installation.
  • Index entries store hex digests instead of full integrity strings (<algo>-<digest>), and the hash algorithm is recorded once per file instead of per entry. This avoids base64 → hex conversion on every CAS path lookup.
  • When the global virtual store is enabled, packages that aren't allowed to build (and don't transitively depend on packages that are) get hashes that don't include the engine name (platform, arch, Node.js major). ~95% of GVS packages now survive Node.js upgrades and architecture changes without re-import.

Performance

A lot of small wins add up to a noticeably faster install:

  • undici replaces node-fetch for all HTTP, with Happy Eyeballs (dual-stack), better keep-alive, and an optimized global dispatcher.
  • Tarball downloads with known size pre-allocate memory to avoid double-copy overhead.
  • The metadata cache is now NDJSON, with If-Modified-Since for conditional fetches.
  • minimumReleaseAge checks use the abbreviated metadata endpoint to fetch less.
  • CAS files are written directly to their content-addressed path instead of via a temp file + rename — saving ~30k rename syscalls per cold install.
  • The staging directory is gone when importing into node_modules.
  • Hot-path string operations in the CAS were tightened, and gunzipSync runs with a larger chunk size for fewer buffer allocations during tarball decompression.
  • During GVS warm reinstalls, redundant internal linking is skipped when no packages were added.

Slimmer runtime installs

Installing a Node.js runtime via node@runtime:<version> (including pnpm env use and pnpm runtime set node) no longer extracts the bundled npm, npx, and corepack from the Node.js archive. That cuts roughly half of the files pnpm has to hash, write to the CAS, and link. If you still need npm, install it as a separate package.

Other notable changes

  • Cleaner script output. pnpm now prints $ command (to stderr, so stdout stays pipe-friendly) instead of > pkg@version stage path\n> command. Project name and path are shown only when running in a different directory.

  • Peer dependency issues are no longer rendered as a tree during install — pnpm suggests running pnpm peers check to view them.

  • Lifecycle scripts no longer get npm_config_* env vars from the pnpm config; only well-known npm_* env vars are set, matching Yarn.

  • pnpm init writes devEngines.packageManager instead of packageManager when init-package-manager is enabled, and the default type is now "module".

  • devEngines.packageManager now supports version ranges; the resolved version is stored in pnpm-lock.yaml and reused if it still satisfies the range.

  • dedupePeers is a new setting that uses version-only suffixes (name@version) instead of full dep paths, eliminating nested suffixes like (foo@1.0.0(bar@2.0.0)) for projects with many recursive peers.

  • pnpm approve-builds now accepts positional arguments for non-interactive use; prefix a name with ! to deny it.

  • Hidden scripts — scripts starting with . can only be called from other scripts and don't show up in pnpm run.

  • -F is a new short alias for --filter.

  • pnpm add short flags-d is now --save-dev, -p is --save-prod, -o is --save-optional, -e is --save-exact (only inside pnpm add).

  • virtualStoreOnly populates the virtual store without creating importer symlinks, hoisting, bin links, or running lifecycle scripts. Useful for pre-populating a store in Nix builds. pnpm fetch now uses this internally.

  • nodeDownloadMirrors in pnpm-workspace.yaml replaces the node-mirror:<channel> .npmrc setting:

    nodeDownloadMirrors:
    release: https://my-mirror.example.com/download/release/
  • Config dependencies are now installed into {storeDir}/links/ and symlinked into node_modules/.pnpm-config/, so they're shared across projects using the same store. Resolved versions and integrity hashes have moved from pnpm-workspace.yaml to a separate document in pnpm-lock.yaml; old inline-hash projects are migrated automatically.

Upgrading

See the full Migrating from v10 to v11 guide for the codemod and manual follow-ups. The short version:

  • Bump your CI and dev environments to Node.js 22+ before upgrading.
  • Move pnpm settings out of .npmrc into pnpm-workspace.yaml (or the global ~/.config/pnpm/config.yaml).
  • Migrate onlyBuiltDependencies and friends to allowBuilds.
  • Migrate auditConfig.ignoreCves to auditConfig.ignoreGhsas.
  • After installing v11, run pnpm setup to update your shell so the new bin subdirectory is on PATH.

The full list of changes is in the changelog. If something you relied on is missing or broken, please open an issue at github.com/pnpm/pnpm/issues.