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:
./node_modules/.bin/— the local project’s installed packagesPATH— globally installed binaries- 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#
# Run a package binary from the registry (downloads if needed)
npx cowsay "Hello from npx"
Output:
_________________
< Hello from npx >
-----------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
# 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):
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#
# Run an exact version of a package
npx typescript@5.4 tsc --version
Output:
Version 5.4.5
# 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:
# 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):
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:
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:
# 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:
{
"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:
npx --no-install prettier --version
Output (if not installed):
npm error could not determine executable to run
Output (if installed locally):
3.2.5
Resolution order in detail#
When you run npx some-tool, resolution happens in this exact order:
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 prettieruses it. - If
prettieris 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:
# 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)
# npx with a locally installed tool — shows which binary is used
npx --no-install prettier --write src/
Output:
src/index.ts 89ms
src/utils.ts 12ms
src/components/Button.tsx 34ms
Common use cases#
Scaffolders — creating new projects#
# 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#
# 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#
# 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#
# 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:
# Show where the npm cache lives
npm config get cache
Output:
/home/user/.npm
# See how much cache space is used
du -sh ~/.npm/_npx/
Output:
148M /home/user/.npm/_npx/
# Clear the entire npm cache (including npx downloads)
npm cache clean --force
Output:
npm warn using --force Recommended protections disabled.
# Verify the cache is intact (without cleaning)
npm cache verify
Output:
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#
| Method | Requires package.json entry | Uses local install | Downloads from registry | Example |
|---|---|---|---|---|
npx cmd | No | Yes (prefers) | Yes (if needed) | npx prettier --write . |
npm run cmd | Yes (script entry) | Yes | No | npm run format |
./node_modules/.bin/cmd | No | Yes | No | ./node_modules/.bin/prettier . |
npm exec -- cmd | No | Yes (prefers) | Yes (if needed) | npm exec -- prettier . |
[!TIP] In CI and scripts that should be reproducible, always prefer
npm runwith a script defined inpackage.json, ornpx --no-installto prevent accidental registry downloads. Reserve plainnpx <tool>for interactive developer use.
[!WARNING] Running
npxwith 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?”.
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:
# Trace npm internals (very verbose)
NODE_DEBUG=cli npx --loglevel=verbose prettier --version 2>&1 | head -30
Output:
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.
ls ~/.npm/_npx | head
Output:
1a2b3c4d5e6f7890
2b3c4d5e6f789012
3c4d5e6f78901234
ls ~/.npm/_npx/1a2b3c4d5e6f7890/node_modules/
Output:
.bin/
.package-lock.json
prettier/
If a cache entry corrupts, delete the specific subdirectory rather than the whole _npx/:
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.
# 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.
# 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:
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-vitebinary lives in packagecreate-vite, buttsclives intypescript). - One command needs binaries from multiple packages (e.g.
ts-node+typescript).
# 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):
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.
# 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:
# .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.
| Feature | npx | pnpm dlx | yarn dlx | bunx |
|---|---|---|---|---|
| Confirmation prompt | Yes (skip with --yes) | No | No | No |
Reads local node_modules | Yes | No (forces fresh) | No (forces fresh) | Yes |
| Cache location | ~/.npm/_npx/<hash> | pnpm store | Yarn cache | Bun store |
| Cache hit speed | Fast | Faster (CAS links) | Fast (zip) | Fastest |
| Sandboxing | None | None | None | None |
| Cold install of a 40-pkg CLI | ~8 s | ~6 s | ~5 s | ~1.5 s |
--package for multi-pkg env | Yes | Yes (--package) | No (use shell) | No |
Works without a package.json | Yes | Yes | Yes | Yes |
| Default version | latest from registry | latest | latest | latest |
# 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)
[!TIP]
pnpm dlxis 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 oflodash) can fetch malicious code if a squatter has registered the typo. - Dependency confusion: if your private
@my-org/fooexists publicly under the same name,npx @my-org/foomay resolve to the public (malicious) version.
Defences:
# 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):
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:
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:
# .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
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.
| Tool | Provided by | Notes |
|---|---|---|
npx | npm 5.2+ | Default; everywhere Node is installed |
pnpm dlx | pnpm | Faster cold start; strict (no local lookup) |
yarn dlx | Yarn v2+ | Zip-cached; Berry-only |
bunx | Bun | Fastest cold start |
deno run | Deno | Direct from URL (deno run npm:prettier) |
# 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#
npxrunning cached old version — when you don’t pin (npx tool), npx prefers cached over latest. Pass--ignore-existingor usenpx tool@latestto bypass the cache.npx <package>hanging on prompt in CI — interactive shells get the “Ok to proceed?” prompt. Always pass--yesin non-interactive contexts.- Local install silently used —
npx toolprefers./node_modules/.bin/toolover the registry. To force a fresh install, use--package=tool@latestorpnpm dlx tool. 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.npxfailing on Windows with paths containing spaces — quote the whole invocation and use forward slashes. Alternatively run from PowerShell which handles spaces better thancmd.exe.- Auth tokens leaking to the cache —
~/.npm/_npx/contents are world-readable by default. Setumask 077for the npm cache or usecache=~/.npm-privatewith restrictive perms. npxnot finding local binaries — happens whennpm installwas interrupted. Recreatenode_modules/.bin:rm -rf node_modules && npm install.- Different behaviour in
npm scriptsvs interactive shell — npm scripts injectnode_modules/.bininto PATH, so plainprettierworks withoutnpx. Outside scripts, you neednpx prettieror 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.
npx --yes create-next-app@latest my-app \
--typescript --tailwind --eslint --app --no-src-dir --import-alias '@/*'
Output:
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.
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:
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.
npx --yes jscodeshift@latest -t ./codemods/rename-prop.js src/
Output:
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.
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:
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.
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.
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.
docker run --rm -it -v "$PWD":/work -w /work node:22-alpine \
sh -c 'npx --yes some-unknown-tool --version'
Output:
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.
# 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.