Cheatsheet

Package manager commands, side by side

One table per task, three columns of commands. Most of npm maps cleanly onto pnpm and bun, but the interesting rows are the ones that do not: frozen lockfile installs, workspace filters, and the flags each tool spells differently. Every row was checked against the current documentation.

Starting a project and installing

Task npm pnpm bun
Create a package.json npm init -y pnpm init bun init
Scaffold from a starter npm create vite@latest pnpm create vite bun create vite
Install everything npm install pnpm install bun install
Reproducible CI install npm ci pnpm install --frozen-lockfile bun install --frozen-lockfile
Skip devDependencies npm ci --omit=dev pnpm install --prod bun install --production
Preview without writing npm install --dry-run pnpm install --lockfile-only bun install --dry-run
Lockfile name package-lock.json pnpm-lock.yaml bun.lock
Import another tool's lockfile not supported pnpm import bun pm migrate

Gotcha: bun switched to the text based bun.lock in version 1.2. The old binary bun.lockb still works but produces unreviewable diffs, so migrate it and delete the binary file. Commit whichever lockfile your tool writes, always.

Adding and removing packages

Task npm pnpm bun
Add a dependency npm install zod pnpm add zod bun add zod
Add a dev dependency npm install -D vitest pnpm add -D vitest bun add -d vitest
Pin an exact version npm install -E zod pnpm add -E zod bun add --exact zod
Add a peer dependency npm install --save-peer react pnpm add --save-peer react bun add --peer react
Add an optional dependency npm install -O fsevents pnpm add -O fsevents bun add --optional fsevents
Install globally npm install -g wrangler pnpm add -g wrangler bun add -g wrangler
Install a specific version npm install react@19.1.0 pnpm add react@19.1.0 bun add react@19.1.0
Install from a git repo npm install user/repo pnpm add user/repo bun add user/repo
Remove a package npm uninstall zod pnpm remove zod bun remove zod
Link a local package npm link pnpm link --global bun link
Patch a dependency patch-package pnpm patch pkg bun patch pkg

Gotcha: the dev dependency flag is the one that catches people moving between tools. npm and pnpm take -D uppercase, bun takes -d lowercase. bun also accepts --dev, which is worth writing out in scripts.

Running scripts and binaries

Task npm pnpm bun
Run a script npm run dev pnpm dev bun run dev
Pass extra arguments npm run test -- --watch pnpm test --watch bun run test --watch
List available scripts npm run pnpm run bun run
Run an installed binary npm exec tsc pnpm exec tsc bun run tsc
Run a package you have not installed npx cowsay hi pnpm dlx cowsay hi bunx cowsay hi
Skip the install prompt npx -y cowsay hi pnpm dlx cowsay hi bunx cowsay hi
Execute a TypeScript file directly node --experimental-strip-types x.ts pnpm dlx tsx x.ts bun x.ts
Path to the bin directory npm bin pnpm bin bun pm bin

Gotcha: the double dash before script arguments is an npm quirk, not a universal one. pnpm and bun forward everything after the script name, so copying an npm command verbatim passes a literal -- through to your tool. Also beware naming a script test or start and then shadowing a package manager subcommand.

Inspecting, auditing, and updating

Task npm pnpm bun
List the dependency tree npm ls pnpm list bun pm ls
Why is this package here npm explain lodash pnpm why lodash bun why lodash
Show outdated packages npm outdated pnpm outdated bun outdated
Scan for vulnerabilities npm audit pnpm audit bun audit
Apply security fixes npm audit fix pnpm audit --fix bun update (manual)
List dependency licenses npx license-checker pnpm licenses list bunx license-checker
Update within semver ranges npm update pnpm update bun update
Jump to the newest majors npx npm-check-updates -u pnpm update --latest bun update --latest
Choose updates interactively npx npm-check -u pnpm update -i bunx npm-check -u
Flatten duplicate versions npm dedupe pnpm dedupe bun install (automatic)
Bump the package version npm version patch pnpm version patch bun pm version patch
Publish to the registry npm publish pnpm publish bun publish

Gotcha: npm update never crosses a major boundary, so a dependency pinned at ^3 stays on 3 forever no matter how many 4.x releases ship. That is why an npm project with no npm-check-updates step slowly rots while looking up to date.

Workspaces and monorepos

Task npm pnpm bun
Declare the workspaces workspaces in package.json pnpm-workspace.yaml workspaces in package.json
Run a script in one package npm run build -w web pnpm --filter web build bun run --filter web build
Run a script everywhere npm run build --workspaces pnpm -r build bun run --filter '*' build
Include dependent packages not supported pnpm --filter web... build not supported
Add a dep to one package npm install zod -w web pnpm --filter web add zod cd packages/web && bun add zod
Add a dep to the root npm install -D turbo pnpm add -Dw turbo bun add -d turbo
Reference a sibling package "ui": "*" "ui": "workspace:*" "ui": "workspace:*"
Force one version everywhere overrides in package.json pnpm.overrides in package.json overrides in package.json

Gotcha: pnpm rewrites workspace:* to a real version range at publish time, so it never leaks into a published package. That protocol is the single strongest argument for pnpm in a monorepo, along with its strict node_modules layout, which fails loudly on a package you never declared.

Cache, config, and cleanup

Task npm pnpm bun
Where the cache lives npm config get cache pnpm store path bun pm cache
Clear the cache npm cache clean --force pnpm store prune bun pm cache rm
Read a config value npm config get registry pnpm config get registry bunfig.toml
Point at a private registry .npmrc .npmrc .npmrc or bunfig.toml
Check who you are logged in as npm whoami pnpm whoami bun pm whoami
Pack a tarball without publishing npm pack pnpm pack bun pm pack
Pin the tool version for the repo packageManager field packageManager field packageManager field
Approve install scripts runs by default pnpm approve-builds bun pm trust pkg

Gotcha: pnpm and bun both block lifecycle scripts from dependencies by default; npm runs them. That default is a real supply chain control, so when an install "silently does nothing", check pnpm approve-builds or bun pm untrusted before blaming the package.

Which one to reach for

Situation Pick
A monorepo with many packages and strict dependency hygiene pnpm
Disk space matters, or you have twenty checkouts of the same app pnpm
You want the fastest cold install and already run bun bun
You want one binary for installing, running, testing, and bundling bun
A CI image or a platform that only ships Node npm
A library published for the widest possible audience npm
A native module that needs a well trodden build path npm or pnpm
Any project with an existing lockfile and a working CI whatever it already uses

Gotcha: never let two managers touch one repo. Two lockfiles disagreeing about a transitive version produces bugs that only reproduce on one machine. Pick one, delete the other lockfile, and pin the choice with the packageManager field so corepack enforces it.

Keep going

The runtime question is separate from the package manager question: Bun vs Node.js covers whether to run bun in production as well as at install time.

These commands sit inside a Dockerfile more often than a terminal, so pair this with the Docker cheatsheet and the Git cheatsheet. Everything else is in the cheatsheet index.