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.