Overview
pnpm installs from a content-addressable store and links packages strictly, so a project can only import what its package.json declares. The migration is mostly mechanical: import the lockfile, then fix the undeclared imports npm’s hoisting hid. pnpm 12 is the current major, a native executable that no longer needs Node after installation; pnpm 11 moved settings out of package.json and .npmrc into YAML. Compare managers in yarn-vs-pnpm.
Prerequisites
- A project with
package-lock.json(Yarn projects can importyarn.lockthe same way). - For a monorepo, the list of workspace globs.
- CI access to change the install step.
Steps
1. Install pnpm
curl -fsSL https://get.pnpm.io/install.sh | sh - # standalone, no Node required
# or: npm install -g pnpm
pnpm --versionPin the version for everyone by adding "packageManager": "pnpm@<version>" to package.json. Do not rely on Corepack: it is no longer bundled with Node from version 25.
2. Import the lockfile
pnpm import # reads package-lock.json, npm-shrinkwrap.json, or yarn.lockThis writes pnpm-lock.yaml with the versions npm already resolved. With workspaces, create pnpm-workspace.yaml first.
3. Install and fix phantom dependencies
rm -rf node_modules && pnpm installA package your code imports but never declared now fails to resolve. Declare it with pnpm add <package>; each one was a latent bug.
4. Approve build scripts
From pnpm 10, dependency lifecycle scripts do not run unless approved. In pnpm 11 and later, approvals live in allowBuilds in pnpm-workspace.yaml, a map from package patterns to booleans, replacing onlyBuiltDependencies. Run pnpm approve-builds when a package that needs a native build step fails.
5. Move settings and workspaces
pnpm 11 and later no longer read the pnpm field of package.json; non-auth settings such as overrides belong in pnpm-workspace.yaml. For a monorepo:
packages:
- "apps/*"
- "packages/*"Remove the workspaces field from the root package.json, which pnpm ignores. Also in pnpm 11, versions published within the last day are not installed (minimumReleaseAge defaults to 1440 minutes).
6. Update CI
- uses: pnpm/action-setup@v6 # version comes from "packageManager"
- uses: actions/setup-node@v7
with: { node-version: 22, cache: pnpm }
- run: pnpm install --frozen-lockfile
- run: pnpm run build && pnpm testpnpm/action-setup@v6 installs only pnpm 11 or newer, and version may be omitted only when packageManager names such a version; otherwise set version explicitly. In pnpm 12, --frozen-lockfile false is replaced by --no-frozen-lockfile. Caching details: set-up-github-actions-cache.
7. Remove the npm lockfile
After CI is green on a branch, delete the old lockfile in the same commit that adds the new one:
git rm package-lock.json && git add pnpm-lock.yaml pnpm-workspace.yamlVerify it worked
rm -rf node_modules && pnpm install --frozen-lockfile && pnpm run build && pnpm testAll three exit 0 and CI passes on a clean runner.
Common errors
ERR_PNPM_OUTDATED_LOCKFILE.pnpm-lock.yamldisagrees withpackage.json; runpnpm installand commit the lockfile.- A module is not found at runtime. A phantom dependency;
pnpm addit. - A native package is not built. Its build script is not approved; see step 4.
- Cross-package imports fail in a monorepo. A glob in
pnpm-workspace.yamlis wrong; check withpnpm ls -r --depth -1. - A just-published fix will not install. The 24-hour release-age delay applies; wait or lower
minimumReleaseAge.
FAQ
Why does my app fail to find modules after switching to pnpm?
pnpm links packages strictly, so a phantom dependency that npm’s hoisting hid no longer resolves. Declare it with pnpm add <package>.
Should I use Corepack to pin the pnpm version?
No. Corepack is no longer bundled with Node from version 25; pin the version with "packageManager": "pnpm@<version>" in package.json.
Why does a native package not build after migrating to pnpm?
From pnpm 10, dependency lifecycle scripts do not run unless approved. Run pnpm approve-builds, which records approvals in allowBuilds in pnpm-workspace.yaml on pnpm 11 and later.
Where do pnpm settings like overrides go now?
In pnpm-workspace.yaml. pnpm 11 and later no longer read the pnpm field of package.json.
Why will pnpm not install a version published today?
pnpm 11 defaults minimumReleaseAge to 1440 minutes, so versions published within the last day are not installed; wait or lower the setting.