npm 09 ๐ Running Executables with npx
npx is the command that resolves the tension between local and global installation. It runs a package’s binary without requiring a permanent install, checking first in the local node_modules/.bin, then in the npm cache, and downloading temporarily only if the binary is not found anywhere . The command ships with npm since version 5.2.0, so no separate installation is needed on any modern Node.js system . The practical effect is that a CLI tool can be invoked from any directory without polluting the global namespace, and a specific version can be selected per invocation with the @version syntax.
The resolution order matters for understanding what npx will actually execute. When you run npx foo, it first checks whether foo exists in the shell’s PATH or in the current project’s node_modules/.bin. If found, it runs that binary directlyโno download, no cache. If not found, it installs the package into a folder in the npm cache, adds that folder to the PATH for the child process, runs the binary, and leaves the package in the cache for future invocations . The cache means the second npx foo is faster than the first, but the package is never added to package.json and never persists in node_modules.
This behavior has a security implication that the npm ecosystem has increasingly acknowledged. Because npx will download and run a package from the registry if the name is not found locally, a typo like npx eslitn (instead of eslint) could execute a malicious package. npm v7 and later added a prompt before installing anything, suppressible with --yes or --no . The npm exec command is the underlying implementation and a safer alternative for CI, because it can be used with a -- separator that prevents argument confusion and signals the intent to run a local tool rather than fetch a remote package . For CI pipelines that run known local tools, npm exec eslint -- . is more explicit than npx eslint ..
Key point: npx runs a binary from the local project first, the cache second, and the registry third. The third case is a supply-chain risk if the package name is mistyped. Use npx for one-off tools and scaffolding; use npm exec with a -- separator in CI when the intent is to run a locally installed dependency.
Why npx exists as a resolution mechanism
The local CLI is not on the PATH. A package installed locally puts its binary in ./node_modules/.bin, which npm adds to the PATH only during npm run script execution. Outside a script, typing the binary name fails. The pre-npx solution was either to install globally or to type the full relative path ./node_modules/.bin/tool, which is verbose and breaks across platforms . npx closes the gap by resolving the binary and executing it in a context where the local .bin is available.
Global installs are a version-lock problem. A globally installed CLI has exactly one version. If project A needs typescript@4.9 and project B needs typescript@5.3, a global install cannot satisfy both. npx typescript@4.9 --version and npx typescript@5.3 --version run different versions without touching the global state . This is the mechanism for testing a tool against multiple versions or for pinning a version in a one-off invocation.
The cache makes repeated invocations cheap. The first npx create-react-app downloads the package to the cache. The second invocation reuses the cached package, so the startup cost is the resolution and process spawn, not the download. The cache is shared across projects, which is why a package downloaded for one project is available for another without a global install.
The trade-off. npx is convenient but its implicit download behavior is a security surface. A package name that is not found locally triggers a registry fetch, and the fetch runs the package’s install scripts and binary. The npm v7 prompt mitigates accidental execution, but the prompt is suppressible and the behavior varies across npm versions . The safer pattern for CI and scripts is npm exec with a -- separator, which signals that the tool is expected to be local and does not silently fetch a remote package .
a. The resolution order and the local-first behavior
The first check is the local project. If npx webpack runs in a project where webpack is a devDependency, the binary at ./node_modules/.bin/webpack is executed. No network access, no cache lookup, no version resolution beyond what is installed. This is the behavior that makes npx useful for running project-local tools without an npm script.
# Install a tool as a dev dependency.
npm install --save-dev webpack
# Run the local binary via npx (no global install).
npx webpack --version
# The same binary via a direct path.
./node_modules/.bin/webpack --version
The second check is the npm cache. If the package was installed by a previous npx invocation, the cached version is used. The cache is keyed by package name and version, so npx cowsay and npx cowsay@1.5.0 may resolve to different cached entries. The --ignore-existing flag forces npx to skip the PATH and local .bin checks and install a fresh version, which is useful when testing a specific version regardless of what is installed .
The third check is the registry. If the package is not found locally or in the cache, npx installs it into a temporary cache folder, adds that folder to the PATH, and runs the binary. The package is not added to node_modules or package.json; it exists only in the cache for future npx invocations .
b. Version selection and the @ syntax
The @ syntax appends a version or tag to the package name. npx create-react-app@3.0.0 my-app runs version 3.0.0 specifically, regardless of what is cached or installed . npx node@10 -v runs Node.js version 10, which is a way to test a script against an older runtime without a version manager . The version specifier is passed to npm’s resolver, so any specifier that npm understands works: exact versions, ranges, tags like @latest, and Git URLs.
# Run a specific version.
npx create-react-app@3.0.0 my-app
# Run a Node.js version directly.
npx node@10 -v
# Run a package from a Git repository.
npx github:user/repo
The --package option is for packages whose binary name differs from the package name, or for packages with multiple binaries. npx --package=foo bar installs the foo package and runs its bar binary. Multiple --package options can be provided, and all specified packages are installed and made available in the PATH of the executed command .
c. npx versus npm exec in CI
The npx binary was rewritten in npm v7 to use npm exec internally, and the standalone npx package was deprecated . The commands are functionally similar for local execution, but they differ in argument parsing. npx treats everything after the package name as arguments to the executed command. npm exec parses its own flags first and requires a -- separator to pass arguments to the tool .
# npx: arguments after the package name go to the tool.
npx eslint . --fix
# npm exec: -- separates npm's arguments from the tool's.
npm exec eslint -- . --fix
The practical difference in CI is intent and safety. npx will fetch a remote package if the local one is not found, which means a typo or a missing dependency could execute code from the registry. npm exec with -- is explicit: it runs the local binary and passes the remaining arguments to it. The Ansible project’s architecture decision record on this topic recommends npm exec for CI because it “eliminates remote execution risk” and “signals ‘run the locally installed tool’ rather than ‘run something, maybe from the internet'” . The trade-off is a slightly more verbose command line.
Complete Example Session
# ============================================
# PART 1: RUN A LOCAL TOOL
# ============================================
# Install as a dev dependency.
npm install --save-dev eslint
# Run the local binary via npx.
npx eslint --version
# No global install. Uses ./node_modules/.bin/eslint.
# ============================================
# PART 2: RUN A ONE-OFF TOOL
# ============================================
# Scaffold a project without installing globally.
npx create-react-app my-app
# First run: downloads to cache, runs.
# Second run: uses cache.
# ============================================
# PART 3: RUN A SPECIFIC VERSION
# ============================================
# Pin the version in the invocation.
npx create-react-app@3.0.0 my-app
# The version is resolved by npm's resolver.
# ============================================
# PART 4: RUN A NODE.JS VERSION
# ============================================
# Test against an older runtime.
npx node@10 -v
# v10.18.1
npx node@12 -v
# v12.14.1
# No nvm required for a one-off check.
# ============================================
# PART 5: THE RESOLUTION ORDER
# ============================================
# npx checks:
# 1. Local node_modules/.bin
# 2. npm cache
# 3. Registry (downloads to cache)
#
# --ignore-existing skips 1 and 2.
# ============================================
# PART 6: PACKAGE WITH DIFFERENT BINARY NAME
# ============================================
# Package "foo" provides binary "bar".
npx --package=foo bar --arg
# --package specifies what to install;
# the positional argument specifies what to run.
# ============================================
# PART 7: NPX VS NPM EXEC
# ============================================
# npx: flags before positional args.
npx eslint . --fix
# npm exec: -- separates npm args from tool args.
npm exec eslint -- . --fix
# In CI, prefer npm exec for explicit local execution.
# ============================================
# PART 8: SECURITY CONSIDERATIONS
# ============================================
# npx prompts before installing remote packages.
# Suppress with -y or --yes (CI).
# Suppress with --no to fail instead.
#
# A typo like "npx eslitn" could execute
# a malicious package if it exists on the registry.
# ============================================
# PART 9: --NO-INSTALL
# ============================================
# Only run if the binary exists locally or in PATH.
npx --no-install eslint --version
# Fails if not found; does not download.
# Deprecated in newer npm in favor of --no.
# ============================================
# PART 10: THE COMPLETE FLOW
# ============================================
# Local tool? โ npx <tool>
# One-off tool? โ npx <package>
# Specific version? โ npx <package>@<version>
# CI with local tool? โ npm exec <tool> -- <args>
# No download allowed? โ npx --no-install
Quick Reference
Resolution Order
| Check | Location | Behavior |
|---|---|---|
| 1 | ./node_modules/.bin | Run local binary |
| 2 | npm cache | Run cached binary |
| 3 | Registry | Download to cache, run |
Common Commands
| Command | Purpose |
|---|---|
npx <pkg> | Run package binary |
npx <pkg>@<version> | Run specific version |
npx --package=<pkg> <cmd> | Run binary with different name |
npx --no-install <pkg> | Only run if already available |
npx -y <pkg> | Auto-confirm install prompt |
npx vs npm exec
| Aspect | npx | npm exec |
|---|---|---|
| Argument parsing | All after package go to tool | -- separates |
| Remote fetch | Yes, with prompt | Yes, with prompt |
| CI intent | Implicit | Explicit with -- |
| npm v7+ | Uses npm exec internally | Native |
Use Cases
| Scenario | Command |
|---|---|
| Local dev tool | npx eslint . |
| Scaffolding | npx create-vite my-app |
| Specific version | npx node@10 -v |
| CI local tool | npm exec eslint -- . |
| No network | npx --no-install tool |
Best Practices
โ Do This:
# Run local tools via npx. // โ
npx eslint .
# Pin a version for one-off tools. // โ
npx create-react-app@3.0.0 my-app
# Use npm exec in CI for explicit local execution. // โ
npm exec eslint -- .
# Use --no-install when network access is prohibited. // โ
npx --no-install eslint --version
# Let npx prompt before downloading remote packages. // โ
npx cowsay "hello"
โ Don’t Do This:
# Assume npx always runs the local version. // โ
# It falls back to the registry if not found.
# Use npx with typos in CI. // โ
# Could execute a malicious package.
# Install every CLI globally instead of using npx. // โ
# npx avoids global version conflicts.
# Use npx for a package that should be a project dependency. // โ
# Install it locally and use npx or npm exec.
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Local CLI not found | npx checks .bin, but tool not installed | npm install --save-dev <tool> |
| Unexpected remote download | Package not in local .bin or cache | Use --no-install or install locally |
| CI hangs on prompt | npx prompts before remote install | Use -y or npm exec |
| Wrong version executed | Cache or local version takes precedence | Use @version or --ignore-existing |
| Argument parsing confusion | npx vs npm exec differ | Use -- with npm exec |
Real-World Examples
1. Run Local ESLint
npx eslint . --fix
2. Scaffold with Create Vite
npx create-vite my-app
3. Specific Version
npx create-react-app@3.0.0 my-app
4. Node Version Test
npx node@10 -v
5. Different Binary Name
npx --package=foo bar --arg
6. No Install
npx --no-install eslint --version
7. CI with npm exec
npm exec eslint -- . --fix
8. Auto-Confirm Install
npx -y cowsay "hello"
9. Git Repository
npx github:user/repo
10. Ignore Existing
npx --ignore-existing cowsay@1.5.0
Visual
Resolution Flow
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ npx <command> โ
โ โ โ
โ โผ โ
โ Exists in local .bin? โ
โ โโโ Yes โ run it โ
โ โโโ No โ Exists in cache? โ
โ โโโ Yes โ run cached โ
โ โโโ No โ Download to cache โ
โ โ run it โ
โ โ
โ The local version always wins. โ
โ The cache avoids repeated downloads. โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
npx vs npm exec
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ARGUMENT PARSING โ
โ โ
โ npx: โ
โ npx eslint . --fix โ
โ Everything after "eslint" goes to eslint. โ
โ โ
โ npm exec: โ
โ npm exec eslint -- . --fix โ
โ "--" separates npm args from tool args. โ
โ โ
โ Without "--", npm exec may parse --fix โ
โ as its own flag. โ
โ โ
โ CI: use npm exec with "--". โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Security Surface
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ REMOTE EXECUTION RISK โ
โ โ
โ npx eslitn โ
โ โ โ
โ โผ โ
โ Not in local .bin. โ
โ Not in cache. โ
โ โ โ
โ โผ โ
โ Downloads "eslitn" from registry. โ
โ Runs its install scripts and binary. โ
โ โ
โ If "eslitn" is a malicious package, โ
โ it executes on your machine. โ
โ โ
โ Mitigations: โ
โ - npm v7+ prompts before install. โ
โ - --no-install prevents download. โ
โ - CI: npm exec with local dependency. โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
When to Use Each
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ TOOL SELECTION โ
โ โ
โ Local dev tool? โ
โ โโโ npx <tool> โ
โ โ
โ One-off scaffolding? โ
โ โโโ npx <package> โ
โ โ
โ Specific version test? โ
โ โโโ npx <package>@<version> โ
โ โ
โ CI with known local tool? โ
โ โโโ npm exec <tool> -- <args> โ
โ โ
โ No network allowed? โ
โ โโโ npx --no-install <tool> โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Resolution order | Local .bin โ cache โ registry |
| First check | ./node_modules/.bin |
| Cache location | npm cache folder |
| Version syntax | npx <pkg>@<version> |
| Different binary | npx --package=<pkg> <cmd> |
| No download | npx --no-install |
| CI alternative | npm exec <tool> -- <args> |
| Prompt | Before remote install (v7+) |
| Shipped with | npm 5.2.0+ |
Key takeaways:
npxruns a binary without a permanent install. It checks the localnode_modules/.binfirst, then the npm cache, then downloads to the cache if not found. The local version always takes precedence .- The cache makes repeated invocations fast. The first
npx create-react-appdownloads; the second uses the cached package. The package is never added topackage.jsonornode_modules. - Version selection uses the
@syntax.npx node@10 -vruns Node.js 10 without a version manager.npx create-react-app@3.0.0pins the scaffolder version . - The resolution order is a security surface. If the binary is not found locally,
npxdownloads it from the registry. A typo could execute a malicious package. npm v7 added a prompt before remote install, and--no-installprevents the download entirely . npm execis the CI-safe alternative. It uses the same underlying implementation but supports a--separator that makes the intent explicit: run the locally installed tool, do not fetch a remote package. The Ansible project’s ADR recommendsnpm execfor this reason .npxis for local tools, one-off scaffolding, and version testing. It is not a replacement for declaring project dependencies. A tool that the project needs should be inpackage.jsonand run vianpxornpm exec; a tool used once should be run vianpxand left in the cache.
Remember: npx is a resolver, not an installer. It finds a binary and runs it, preferring the local project, then the cache, then the registry. The local-first behavior means a project’s pinned version of a tool is what runs, not a global version or a fresh download. The remote fallback is convenient for one-off tools and scaffolding, but it is also a supply-chain surface: a mistyped package name can execute code from the registry. Use npx for the tools you run ad hoc, use npm exec with -- in CI for the tools that should be local, and use --no-install when the environment cannot be trusted to fetch.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!