The complete pnpm guide — the global package store, strict node_modules, pnpm-lock.yaml, scripts, workspaces, migrating from npm, Corepack, and publishing.
Why: pnpm stores each package version once on disk and symlinks it into every project that needs it — big speed and disk savings, especially across many projects. Its strict node_modules also catches dependencies you forgot to declare. It installs from the same npm registry.
Enable Corepack once (bundled with Node.js), then pnpm just works
corepack enablepnpm --versionWhy: package.json is the manifest of your project — its name, version, dependencies, and scripts. Every project starts here. Note: you only do this for projects you start from scratch — frameworks like Next.js generate package.json for you when you scaffold the project.
pnpm initNote: you will edit this file constantly — these are the fields that matter day to day. "type": "module" makes .js files use import/export (ESM) instead of require(); new projects should use it.
A typical package.json
{
"name": "my-app",
"version": "1.0.0",
"type": "module",
"packageManager": "pnpm@9.15.0",
"scripts": {
"dev": "next dev",
"build": "next build",
"test": "vitest"
},
"dependencies": {
"react": "^19.0.0"
},
"devDependencies": {
"typescript": "^5.6.0"
},
"engines": {
"node": ">=20"
}
}Why: dependencies are packages your app needs at runtime; devDependencies are tools only needed while developing — test runners, linters, bundlers. Production installs can skip devDependencies entirely.
Runtime dependency
pnpm add reactDev-only dependency (-D is short for --save-dev)
pnpm add -D typescriptProduction install — skips devDependencies
pnpm install --prodWhy: after cloning a project, pnpm install downloads everything. outdated shows current vs wanted (allowed by your range) vs latest. up --latest may cross majors — use with care.
Install all dependencies from package.json
pnpm installpnpm add "package"pnpm remove "package"What can be updated?
pnpm outdatedUpdate within your semver ranges
pnpm upUpdate everything to latest — may cross majors
pnpm up --latestWhy: global installs are for CLI tools you use across projects (not project dependencies). dlx runs a package binary without installing it at all — perfect for scaffolding tools you use once.
pnpm add -g "package"Run without installing
pnpm dlx create-next-app@latestWhy: semver is a contract — breaking changes bump major, new features bump minor, bug fixes bump patch. ^19.2.1 accepts any 19.x.x (>= 19.2.1), ~19.2.1 only 19.2.x, and 19.2.1 alone means exactly that version. The caret is the default — this is why installs can differ without a lockfile.
19.2.1 19 = major: breaking changes 2 = minor: new features, backwards compatible 1 = patch: bug fixes only
Default — caret range
pnpm add reactExact version, no range
pnpm add react@19.2.1 --save-exactInstall a specific older major
pnpm add react@18Why: package.json says "react: ^19.0.0" — a range. pnpm-lock.yaml records the exact version of every package in the tree, including dependencies of dependencies, so every install resolves identically. Always commit it. In CI, --frozen-lockfile installs exactly what it says and fails on drift.
git add pnpm-lock.yamlgit commit -m "Update lockfile"Clean install for CI pipelines and Dockerfiles
pnpm install --frozen-lockfileWhy: the scripts field is the standard place for project commands — anyone can clone your repo and know that dev, build, and test will work. A script named prebuild runs automatically before build, postbuild after it. pnpm forwards arguments directly and lets you drop the "run".
pnpm devpnpm buildpnpm testPass arguments through
pnpm test --watchWhy: a workspace is multiple packages in one repo — apps and shared libraries — sharing a single lockfile and install. pnpm is the most popular choice for monorepos.
Create a pnpm-workspace.yaml at the repo root with:
packages:
- "apps/*"
- "packages/*"One install at the root wires everything
pnpm installFilter by package name, or run recursively in all of them
pnpm --filter web buildpnpm -r buildNote: audit checks your tree against a database of known vulnerabilities. Typosquatting and compromised packages are real — and pnpm v10+ blocks dependency postinstall scripts by default; allowlist only the ones you trust.
pnpm auditAllowlist trusted build scripts in package.json: "pnpm": { "onlyBuiltDependencies": ["esbuild"] }
Why: switching an existing project is cheap — pnpm converts your existing lockfile so resolved versions stay identical.
Converts package-lock.json to pnpm-lock.yaml
pnpm importrm package-lock.jsonpnpm install