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 import yarn.lock the 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 --version

Pin 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.lock

This 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 install

A 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 test

pnpm/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.yaml

Verify it worked

rm -rf node_modules && pnpm install --frozen-lockfile && pnpm run build && pnpm test

All three exit 0 and CI passes on a clean runner.

Common errors

  • ERR_PNPM_OUTDATED_LOCKFILE. pnpm-lock.yaml disagrees with package.json; run pnpm install and commit the lockfile.
  • A module is not found at runtime. A phantom dependency; pnpm add it.
  • 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.yaml is wrong; check with pnpm 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.