cheat sheet

npx

Run npm package binaries without global installs using npx. Covers resolution order, version pinning, --yes, --no-install, npm exec, scaffolders, and cache management.

npx — Execute npm Packages

What it is

npx is an npm package runner bundled with npm since v5.2 (Node 8.2+). It lets you execute package binaries without installing them globally. When you run npx some-tool, it resolves the binary in this order:

  1. ./node_modules/.bin/ — the local project's installed packages
  2. PATH — globally installed binaries
  3. The npm registry — downloads and caches the package temporarily, then runs it

This means you can run any CLI tool from the registry without a global install and always get the version you specify.

Basic usage

bash
# Run a package binary from the registry (downloads if needed)
npx cowsay "Hello from npx"

Output:

text
 _________________
< Hello from npx >
 -----------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
bash
# Scaffold a new project
npx create-react-app my-app
npx create-next-app@latest my-next-app
npx create-vite my-vite-app

Output (npx create-next-app@latest my-next-app):

text
Need to install the following packages:
  create-next-app@15.3.0
Ok to proceed? (y) y

✔ Would you like to use TypeScript? … Yes
✔ Would you like to use ESLint? … Yes
✔ Would you like to use Tailwind CSS? … Yes
✔ Would you like to use `src/` directory? … Yes
✔ Would you like to use App Router? … Yes
✔ Would you like to customize the default import alias (@/*)? … No
Creating a new Next.js app in /home/user/my-next-app.

Specifying a version

bash
# Run an exact version of a package
npx typescript@5.4 tsc --version

Output:

text
Version 5.4.5
bash
# Run with a semver range
npx eslint@^8 --version
npx prettier@3 --version

# Always use the latest (bypass cache)
npx --yes create-vite@latest my-app

Output: (none — exits 0 on success)

--package flag

Use --package when the binary name differs from the package name, or to install multiple packages for a single command:

bash
# Install typescript first, then run tsc
npx --package=typescript -- tsc --init

# Multiple packages in one invocation
npx --package=typescript --package=ts-node -- ts-node script.ts

Output (npx --package=typescript -- tsc --init):

text
Created a new tsconfig.json with:
  target: es2016
  module: commonjs
  strict: true
  esModuleInterop: true
  ...

You can learn more at https://aka.ms/tsconfig

--yes flag (skip confirmation)

By default, npx asks for confirmation before downloading a package not already installed. Use --yes (or -y) to skip that prompt in scripts and CI:

bash
npx --yes create-next-app@latest my-app
npx -y prettier@3 --write .

Output: (none — exits 0 on success)

npm exec vs npx

Since npm 7, npm exec and npx are functionally identical. The npm exec form is more explicit and preferred in npm scripts:

bash
# These are equivalent
npx tsc --version
npm exec -- tsc --version
npm exec --package=typescript -- tsc --version

Output: (none — exits 0 on success)

In package.json scripts, prefer npm exec over npx:

json
{
  "scripts": {
    "typecheck": "npm exec -- tsc --noEmit",
    "scaffold": "npm exec --yes --package=plop -- plop"
  }
}

--no-install flag

Fail immediately if the binary is not already installed locally — useful in CI to prevent surprise downloads:

bash
npx --no-install prettier --version

Output (if not installed):

text
npm error could not determine executable to run

Output (if installed locally):

text
3.2.5

Resolution order in detail

When you run npx some-tool, resolution happens in this exact order:

text
1. ./node_modules/.bin/some-tool     ← local node_modules (fastest, preferred)
2. $PATH (globally installed binaries)
3. npm registry download → ~/.npm/_npx/<hash>/node_modules/.bin/some-tool

This means:

  • If you have a local install (npm install --save-dev prettier), npx prettier uses it.
  • If prettier is not local but is in your $PATH (global install), that runs.
  • Otherwise, npx downloads it from the registry, caches it, and runs it.

Running local binaries

The three equivalent ways to run a locally installed binary:

bash
# 1. npx (recommended — readable, handles PATH automatically)
npx tsc --version

# 2. Direct path (verbose but unambiguous)
./node_modules/.bin/tsc --version

# 3. npm run (requires a script entry in package.json)
npm run typecheck

Output: (none — exits 0 on success)

bash
# npx with a locally installed tool — shows which binary is used
npx --no-install prettier --write src/

Output:

text
src/index.ts 89ms
src/utils.ts 12ms
src/components/Button.tsx 34ms

Common use cases

Scaffolders — creating new projects

bash
# React
npx create-react-app my-app
npx create-react-app my-app --template typescript

# Next.js
npx create-next-app@latest my-next-app

# Vite
npx create-vite my-vite-app
npx create-vite my-vite-app --template react-ts

# Astro
npx create-astro my-astro-site

# Clone a GitHub template without git history (degit)
npx degit user/repo my-app
npx degit github:user/repo#branch my-app

# Download a template (giget — supports GitHub, GitLab, Bitbucket)
npx giget gh:user/repo my-app

Output: (none — exits 0 on success)

One-off formatting and linting

bash
# Format all files without a local install
npx prettier@3 --write .

# Lint with ESLint
npx eslint src/

# Run a TypeScript type check
npx typescript@5 tsc --noEmit

# Convert a project to ESM
npx esm-to-esm src/

Output: (none — exits 0 on success)

Code generation and migration tools

bash
# Generate a component with plop
npx plop component MyButton

# Run codemods (e.g., React 18 migration)
npx react-codemod rename-unsafe-lifecycles src/

# OpenAPI client generation
npx @openapitools/openapi-generator-cli generate \
  -i openapi.yaml -g typescript-fetch -o src/generated

Output: (none — exits 0 on success)

Inspecting packages without installing

bash
# Check what files a package contains before installing
npx npm-check-updates

# Visualize your dependency tree
npx npmgraph .

# Find outdated or duplicate packages
npx depcheck

Output: (none — exits 0 on success)

Cache management

npx caches downloaded packages in the npm cache directory so repeat invocations are fast:

bash
# Show where the npm cache lives
npm config get cache

Output:

text
/home/user/.npm
bash
# See how much cache space is used
du -sh ~/.npm/_npx/

Output:

text
148M    /home/user/.npm/_npx/
bash
# Clear the entire npm cache (including npx downloads)
npm cache clean --force

Output:

text
npm warn using --force Recommended protections disabled.
bash
# Verify the cache is intact (without cleaning)
npm cache verify

Output:

text
Cache verified and compressed (~/.npm):
Content verified: 1432 (61.2MB)
Index entries: 2011
Finished in 4.231s

Comparison: npx vs npm run vs direct path

MethodRequires package.json entryUses local installDownloads from registryExample
npx cmdNoYes (prefers)Yes (if needed)npx prettier --write .
npm run cmdYes (script entry)YesNonpm run format
./node_modules/.bin/cmdNoYesNo./node_modules/.bin/prettier .
npm exec -- cmdNoYes (prefers)Yes (if needed)npm exec -- prettier .

In CI and scripts that should be reproducible, always prefer npm run with a script defined in package.json, or npx --no-install to prevent accidental registry downloads. Reserve plain npx <tool> for interactive developer use.

Running npx with an unrecognized package name downloads and executes arbitrary code. Always verify the package name matches the official package before running — typosquatting attacks exist on the npm registry.

Resolution algorithm — deep-dive

The full resolution sequence npx uses when you invoke npx some-tool. Understanding this matters when a tool runs the wrong version, fails silently, or pulls from an unexpected registry. Reach for this section when debugging "why did npx run the wrong thing?".

text
1. Parse the invocation
   ↓
   npx <flags> <pkg-spec> -- <args>
   ↓
   Strip flags; extract pkg-spec.
   ↓
2. Resolve binary name
   ↓
   - If pkg-spec contains '/', '@', or starts with '.', treat as package
   - Otherwise treat as binary name
   ↓
3. Check local node_modules (./node_modules/.bin/)
   ↓ Hit?  → run it. Done.
   ↓ Miss?
4. Check ancestor node_modules (walk up to /)
   ↓ Hit?  → run it. Done.
   ↓ Miss?
5. Check $PATH (system PATH, including nvm/global installs)
   ↓ Hit?  → run it. Done.
   ↓ Miss?
6. Check npm cache (~/.npm/_npx/<hash>)
   ↓ Hit?  → run cached version. Done.
   ↓ Miss?
7. Download from the registry into ~/.npm/_npx/<hash>
   ↓ Prompt for confirmation (skip with --yes)
   ↓ Install (respects .npmrc, scopes, auth)
   ↓ Run the binary from the temp install.

Verify which path npx takes:

bash
# Trace npm internals (very verbose)
NODE_DEBUG=cli npx --loglevel=verbose prettier --version 2>&1 | head -30

Output:

text
npm verbose cli /usr/lib/node_modules/npm/bin/npx-cli.js
npm verbose exec packages: ["prettier"]
npm verbose exec args: ["--version"]
npm http fetch GET 200 https://registry.npmjs.org/prettier 18ms
3.3.3

Hash-based cache directory

Each invocation gets a stable cache directory keyed by an MD5 of the npx package spec. Repeat invocations are instant after the first.

bash
ls ~/.npm/_npx | head

Output:

text
1a2b3c4d5e6f7890
2b3c4d5e6f789012
3c4d5e6f78901234
bash
ls ~/.npm/_npx/1a2b3c4d5e6f7890/node_modules/

Output:

text
.bin/
.package-lock.json
prettier/

If a cache entry corrupts, delete the specific subdirectory rather than the whole _npx/:

bash
rm -rf ~/.npm/_npx/1a2b3c4d5e6f7890

Output: (none — exits 0 on success)

Difference between local resolution and npm exec

Plain npx tool does resolution 1→7. npm exec --no-install -- tool stops at step 5. npm exec --package=tool -- tool skips steps 1→5 and goes straight to step 6/7.

bash
# Use only the locally-installed eslint (fast, deterministic)
npm exec --no-install -- eslint --version

# Force a fresh download, ignore any local install
npm exec --yes --package=eslint@latest -- eslint --version

Output: (none — exits 0 on success)

-c / --call — chain multiple commands

--call (or -c) runs an arbitrary shell-style command inside a context where the installed binary is on PATH. Useful for one-line chains where the package provides multiple binaries.

bash
# Install create-vite + run a follow-up
npx -c 'create-vite my-app --template vue && cd my-app && npm install'

# Run several binaries from a single package
npx --package=typescript -c 'tsc --version && tsserver --version'

Output:

text
Version 5.5.4
TypeScript Server 5.5.4

The shell that interprets -c is the one in $SHELL (or cmd.exe on Windows). For cross-platform robustness, prefer chaining at the npm script level.

Multiple packages with --package

--package (alias -p) installs additional packages into the temporary environment before running the binary. Useful when:

  • The binary name differs from the package name (e.g. create-vite binary lives in package create-vite, but tsc lives in typescript).
  • One command needs binaries from multiple packages (e.g. ts-node + typescript).
bash
# Install typescript first, run tsc from it
npx --package=typescript -- tsc --init

# Multiple packages — install both, then run ts-node
npx --package=typescript --package=ts-node -- ts-node script.ts

# Pin versions on each
npx --package=typescript@5.5 --package=ts-node@10.9 -- ts-node script.ts

Output (npx --package=typescript -- tsc --init):

text
Created a new tsconfig.json with:
  target: es2016
  module: commonjs
  strict: true
  esModuleInterop: true
You can learn more at https://aka.ms/tsconfig

Specifying a registry per-invocation

When a tool lives on a private registry, pass --registry or use .npmrc. The flag wins over .npmrc.

bash
# One-off from GitHub Packages
npx --registry=https://npm.pkg.github.com @my-org/codegen build

# From a corporate proxy
npx --registry=https://npm.corp.example.com prettier --check .

Output: (none — exits 0 on success)

For long-term setup, put it in .npmrc:

ini
# .npmrc
@my-org:registry=https://npm.pkg.github.com/
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}

Comparison: npx vs pnpm dlx vs yarn dlx vs bunx

All four are "run a CLI from a package without installing", but they cache differently and have different defaults.

Featurenpxpnpm dlxyarn dlxbunx
Confirmation promptYes (skip with --yes)NoNoNo
Reads local node_modulesYesNo (forces fresh)No (forces fresh)Yes
Cache location~/.npm/_npx/<hash>pnpm storeYarn cacheBun store
Cache hit speedFastFaster (CAS links)Fast (zip)Fastest
SandboxingNoneNoneNoneNone
Cold install of a 40-pkg CLI~8 s~6 s~5 s~1.5 s
--package for multi-pkg envYesYes (--package)No (use shell)No
Works without a package.jsonYesYesYesYes
Default versionlatest from registrylatestlatestlatest
bash
# Same task, four tools
npx     create-vite my-app
pnpm dlx create-vite my-app
yarn dlx create-vite my-app
bunx     create-vite my-app

Output: (none — exits 0 on success)

pnpm dlx is the strictest of the four — it never falls back to a locally-installed copy and always pulls a fresh resolution. Use it when reproducibility outweighs speed (e.g. running a scaffolder that must not pick up an old local install).

See pnpm, yarn, and bun for the host tools.

Security considerations

npx downloads and executes code from the network. Treat every invocation as curl | sh of code you didn't write.

Typosquatting and dependency confusion

  • Typo: npx loadsh (instead of lodash) can fetch malicious code if a squatter has registered the typo.
  • Dependency confusion: if your private @my-org/foo exists publicly under the same name, npx @my-org/foo may resolve to the public (malicious) version.

Defences:

bash
# Always pin a version
npx prettier@3.3.3 --check .

# Use --package to be explicit about the package, not the binary name
npx --package=prettier@3.3.3 -- prettier --check .

# Inspect before running
npm view prettier@3.3.3

Output (npm view prettier@3.3.3):

text
prettier@3.3.3 | MIT | deps: none | versions: 158
Prettier is an opinionated code formatter
https://prettier.io
keywords: format, prettier
dist
.tarball: https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz
.shasum: 7c54fd35e9d8e21ec1b16f1c75f5db59c1c4f17e
.integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==

--ignore-scripts

Some packages run install scripts that touch the filesystem. Disable them when you're just trying to execute a binary:

bash
npx --ignore-scripts prettier@3.3.3 --check .

Output: (none — exits 0 on success)

Pinning in CI

CI scripts that use plain npx <tool> are non-deterministic — the package can update between runs. Pin or vendor:

yaml
# .github/workflows/ci.yml — pin npx tool versions
- run: npx --yes prettier@3.3.3 --check .
- run: npx --yes typescript@5.5.4 tsc --noEmit
bash
npx --yes prettier@3.3.3 --check .

Output: (none — exits 0 on success)

npx without npm — alternatives

If you don't have npm installed, several drop-in alternatives can run packages from the npm registry.

ToolProvided byNotes
npxnpm 5.2+Default; everywhere Node is installed
pnpm dlxpnpmFaster cold start; strict (no local lookup)
yarn dlxYarn v2+Zip-cached; Berry-only
bunxBunFastest cold start
deno runDenoDirect from URL (deno run npm:prettier)
bash
# Run prettier from Deno without a Node install
deno run --allow-read --allow-write npm:prettier@3.3.3 --check .

Output: (none — exits 0 on success)

See deno for the npm: specifier system.

Common pitfalls

  1. npx running cached old version — when you don't pin (npx tool), npx prefers cached over latest. Pass --ignore-existing or use npx tool@latest to bypass the cache.
  2. npx <package> hanging on prompt in CI — interactive shells get the "Ok to proceed?" prompt. Always pass --yes in non-interactive contexts.
  3. Local install silently usednpx tool prefers ./node_modules/.bin/tool over the registry. To force a fresh install, use --package=tool@latest or pnpm dlx tool.
  4. npx <binary> saying "could not determine executable" — the binary name doesn't match the package name. Use --package=<pkg> -- <binary>. Example: npx --package=@org/cli -- cli build.
  5. npx failing on Windows with paths containing spaces — quote the whole invocation and use forward slashes. Alternatively run from PowerShell which handles spaces better than cmd.exe.
  6. Auth tokens leaking to the cache~/.npm/_npx/ contents are world-readable by default. Set umask 077 for the npm cache or use cache=~/.npm-private with restrictive perms.
  7. npx not finding local binaries — happens when npm install was interrupted. Recreate node_modules/.bin: rm -rf node_modules && npm install.
  8. Different behaviour in npm scripts vs interactive shell — npm scripts inject node_modules/.bin into PATH, so plain prettier works without npx. Outside scripts, you need npx prettier or the full path.

Real-world recipes

One-line project scaffold

The most common use of npx — bootstrap a new project from a template with no prior install.

bash
npx --yes create-next-app@latest my-app \
  --typescript --tailwind --eslint --app --no-src-dir --import-alias '@/*'

Output:

text
Creating a new Next.js app in /home/alice/my-app.
Installing dependencies:
- react
- react-dom
- next

added 367 packages in 14s
Success! Created my-app at /home/alice/my-app

Compare two versions of a tool

Useful when debugging a regression — run the same input against two versions side by side.

bash
npx prettier@3.2.5 --version
npx prettier@3.3.3 --version
diff <(npx prettier@3.2.5 --check src/) <(npx prettier@3.3.3 --check src/)

Output:

text
3.2.5
3.3.3

Run a one-shot codemod

Codemods rewrite source files at scale. jscodeshift powers most of them and is best run via npx so the version matches the codemod's expectations.

bash
npx --yes jscodeshift@latest -t ./codemods/rename-prop.js src/

Output:

text
Processing 142 files...
All done.
Results:
  0 errors
  37 unmodified
  105 modified

Pinned scaffolder + post-init script

Combine --package and -c for a deterministic project setup.

bash
npx --yes -p create-vite@5.5.0 -p typescript@5.5.4 \
  -c 'create-vite my-app --template react-ts && cd my-app && npm install && npm run typecheck'

Output:

text
Scaffolding project in /home/alice/my-app
Done. Now run:
  cd my-app
  npm install
  npm run dev

added 220 packages in 9s
> tsc --noEmit

Bypass cache when latest matters

You always want the freshest release for security tools.

bash
npx --yes --ignore-existing npm-audit-html@latest --output report.html

Output: (none — exits 0 on success)

Run from a GitHub repo (no registry needed)

npx accepts a github:user/repo spec — Useful for testing PRs or running unpublished tools.

bash
npx github:alicedev/my-tool#feature-branch

Output: (none — exits 0 on success)

Sandbox a dangerous tool

When auditing an unknown package, run inside a container so it can't touch your home directory.

bash
docker run --rm -it -v "$PWD":/work -w /work node:22-alpine \
  sh -c 'npx --yes some-unknown-tool --version'

Output:

text
some-unknown-tool 1.0.0

Replace global installs

Global tools age badly — they accumulate, version-drift, and pollute PATH. Replace npm install -g with npx (pinned) at the call site.

bash
# Before: npm install -g typescript prettier eslint
# After:
npx --yes typescript@5.5.4 tsc --version
npx --yes prettier@3.3.3 --check .
npx --yes eslint@9.10.0 src/

Output: (none — exits 0 on success)

Or commit the versions to package.json devDependencies and use npm exec / npm run — no npx needed.