Migration to pnpm
Guide for migrating existing projects from npm or Yarn to pnpm, plus upgrading pnpm v10 → v11 and v11 → v12.
Upgrading pnpm v11 → v12
pnpm 12 is a Rust rewrite and is stable. It keeps v11's commands, flags, settings, and lockfile format — upgrading is not a migration. Only these differ (six change a result, one fails outright):
pnpm install --resolution-onlyis removed and errors. Usepnpm peers check(reads issues from the lockfile). Grep CI scripts for it before switching.- Git dependency resolution: GitHub/GitLab/Bitbucket specifiers resolve via the host's HTTPS URL and pnpm never records an SSH URL — one lockfile works with or without SSH keys. Run
pnpm update <pkg>once to re-resolve an oldgit@…entry. For private SSH, usegit config --global url."git@github.com:".insteadOf https://github.com/. - Naming a package manager installs the tool, not the npm package:
pnpm add -g yarninstalls Yarn (not the Classicyarnpackage),pnx node@22runs that Node.js release. A specifier likeyarn@npm:yarn@1.22.22still installs the package. - Project-aware global bins: a global
node/deno/bunruns the version the current project pins. packageImportMethod: autohardlinks first on Linux (was clone-first). Useclone/clone-or-copyif you edit files innode_modules.- Cyclic dependency graphs produce deterministic lockfiles; the first re-resolving install rewrites cyclic peer variants (one-time diff).
engineStrictnow fails when a package depends through regulardependencieson an incompatible engine even under anoptionalDependenciessubtree.
lateston npm still points at the v11 line; install v12 from thelatest-12tag until it graduates.
Upgrading pnpm v10 → v11
v11 changes how configuration is read. Most of it is mechanical — run the codemod:
cd /path/to/project
pnpx codemod run pnpm-v10-to-v11The codemod automatically:
- Moves
package.json#pnpmsettings intopnpm-workspace.yaml(thepnpmfield is no longer read). - Splits
.npmrc: only auth/registry settings stay in.npmrc; every other key moves topnpm-workspace.yamlas camelCase (e.g.node-linker→nodeLinker). Per-subproject.npmrcfiles becomepackageConfigs["<name>"]. - Consolidates build settings (
onlyBuiltDependencies,neverBuiltDependencies,ignoredBuiltDependencies,onlyBuiltDependenciesFile) into oneallowBuilds: { name: true|false }map. - Replaces
managePackageManagerVersions/packageManagerStrict/packageManagerStrictVersionwithpmOnFail: download|ignore|warn|error. - Renames
allowNonAppliedPatches→allowUnusedPatches,auditConfig.ignoreCves→auditConfig.ignoreGhsas. - Converts
useNodeVersion→devEngines.runtime, and bumpspackageManager.
Manual follow-ups (not automatable):
- Convert
CVE-…IDs toGHSA-…inauditConfig.ignoreGhsas. ignorePatchFailuresremoved — failed patches now always throw.npm_config_*env vars →pnpm_config_*(CI, shell profiles, Docker).pnpm link <name>→ use a path (pnpm link ./foo);pnpm link --global→pnpm add -g ..pnpm install -g(no args) andpnpm serverremoved.- A
package.jsonscript namedclean/setup/deploy/rebuildnow shadows the built-in — usepnpm pm <name>for the built-in.
Migrating from npm / Yarn
Quick Migration
From npm
# Remove npm lockfile and node_modules
rm -rf node_modules package-lock.json
# Install with pnpm
pnpm installFrom Yarn
# Remove yarn lockfile and node_modules
rm -rf node_modules yarn.lock
# Install with pnpm
pnpm installImport Existing Lockfile
pnpm can import existing lockfiles:
# Import from npm or yarn lockfile
pnpm import
# This creates pnpm-lock.yaml from:
# - package-lock.json (npm)
# - yarn.lock (yarn)
# - npm-shrinkwrap.json (npm)Handling Common Issues
Phantom Dependencies
pnpm is strict about dependencies. If code imports a package not in package.json, it will fail.
Problem:
// Works with npm (hoisted), fails with pnpm
import lodash from 'lodash' // Not in dependencies, installed by another packageSolution: Add missing dependencies explicitly:
pnpm add lodashMissing Peer Dependencies
pnpm reports peer dependency issues by default.
Option 1: Let pnpm auto-install (default in v8+):
autoInstallPeers: trueOption 2: Install manually:
pnpm add react react-domOption 3: Suppress warnings if acceptable:
peerDependencyRules:
ignoreMissing:
- reactSymlink Issues
Some tools don't work with symlinks. Use hoisted mode:
nodeLinker: hoistedOr hoist specific packages:
publicHoistPattern:
- '*eslint*'
- '*babel*'Native Module Rebuilds
If native modules fail, try:
# Rebuild all native modules
pnpm rebuild
# Or reinstall
rm -rf node_modules
pnpm installMonorepo Migration
From npm Workspaces
Create
pnpm-workspace.yaml:packages: - 'packages/*'Update internal dependencies to use workspace protocol:
{ "dependencies": { "@myorg/utils": "workspace:^" } }Install:
rm -rf node_modules packages/*/node_modules package-lock.json pnpm install
From Yarn Workspaces
Remove Yarn-specific files:
rm yarn.lock .yarnrc.yml rm -rf .yarnCreate
pnpm-workspace.yamlmatchingworkspacesin package.json:packages: - 'packages/*'Update
package.json- remove Yarn workspace config if not needed:{ // Remove "workspaces" field (optional, pnpm uses pnpm-workspace.yaml) }Convert workspace references:
// From Yarn "@myorg/utils": "*" // To pnpm "@myorg/utils": "workspace:*"
From Lerna
pnpm can replace Lerna for most use cases:
# Lerna: run script in all packages
lerna run build
# pnpm equivalent
pnpm -r run build
# Lerna: run in specific package
lerna run build --scope=@myorg/app
# pnpm equivalent
pnpm --filter @myorg/app run build
# Lerna: publish
lerna publish
# pnpm: use changesets instead
pnpm add -Dw @changesets/cli
pnpm changeset
pnpm changeset version
pnpm publish -rConfiguration Migration
Keep only auth/registry in .npmrc; put everything else in pnpm-workspace.yaml (camelCase).
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
//npm.myorg.com/:_authToken=${MYORG_TOKEN}registries:
default: https://registry.npmjs.org/
'@myorg': https://npm.myorg.com/
autoInstallPeers: true
strictPeerDependencies: falseScripts Migration
Most scripts work unchanged. Update pnpm-specific patterns:
{
"scripts": {
// npm: recursive scripts
"build:all": "npm run build --workspaces",
// pnpm: use -r flag
"build:all": "pnpm -r run build",
// npm: run in specific workspace
"dev:app": "npm run dev -w packages/app",
// pnpm: use --filter
"dev:app": "pnpm --filter @myorg/app run dev"
}
}CI/CD Migration
Update CI configuration:
# Before (npm)
- run: npm ci
# After (pnpm)
- uses: pnpm/action-setup@v4
- run: pnpm install --frozen-lockfile # or: pnpm ciAdd to package.json for Corepack:
{
"packageManager": "pnpm@10.0.0"
}Gradual Migration
For large projects, migrate gradually:
- Start with CI: Use pnpm in CI, keep npm/yarn locally
- Add pnpm-lock.yaml: Run
pnpm importto create lockfile - Test thoroughly: Ensure builds work with pnpm
- Update documentation: Update README, CONTRIBUTING
- Remove old files: Delete old lockfiles after team adoption
Rollback Plan
If migration causes issues:
# Remove pnpm files
rm -rf node_modules pnpm-lock.yaml pnpm-workspace.yaml
# Restore npm
npm install
# Or restore Yarn
yarn installKeep old lockfile in git history for easy rollback.
<!-- Source references: - https://pnpm.io/migration - https://pnpm.io/cli/import - https://pnpm.io/configuring -->