| |

npm 09 ๐ŸŒ Running Executables with npx

โฑ๏ธ Estimated read: 13 min | |

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

CheckLocationBehavior
1./node_modules/.binRun local binary
2npm cacheRun cached binary
3RegistryDownload to cache, run

Common Commands

CommandPurpose
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

Aspectnpxnpm exec
Argument parsingAll after package go to tool-- separates
Remote fetchYes, with promptYes, with prompt
CI intentImplicitExplicit with --
npm v7+Uses npm exec internallyNative

Use Cases

ScenarioCommand
Local dev toolnpx eslint .
Scaffoldingnpx create-vite my-app
Specific versionnpx node@10 -v
CI local toolnpm exec eslint -- .
No networknpx --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

PitfallWhy It HappensFix
Local CLI not foundnpx checks .bin, but tool not installednpm install --save-dev <tool>
Unexpected remote downloadPackage not in local .bin or cacheUse --no-install or install locally
CI hangs on promptnpx prompts before remote installUse -y or npm exec
Wrong version executedCache or local version takes precedenceUse @version or --ignore-existing
Argument parsing confusionnpx vs npm exec differUse -- 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

ItemValue
Resolution orderLocal .bin โ†’ cache โ†’ registry
First check./node_modules/.bin
Cache locationnpm cache folder
Version syntaxnpx <pkg>@<version>
Different binarynpx --package=<pkg> <cmd>
No downloadnpx --no-install
CI alternativenpm exec <tool> -- <args>
PromptBefore remote install (v7+)
Shipped withnpm 5.2.0+

Key takeaways:

  • npx runs a binary without a permanent install. It checks the local node_modules/.bin first, 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-app downloads; the second uses the cached package. The package is never added to package.json or node_modules.
  • Version selection uses the @ syntax. npx node@10 -v runs Node.js 10 without a version manager. npx create-react-app@3.0.0 pins the scaffolder version .
  • The resolution order is a security surface. If the binary is not found locally, npx downloads it from the registry. A typo could execute a malicious package. npm v7 added a prompt before remote install, and --no-install prevents the download entirely .
  • npm exec is 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 recommends npm exec for this reason .
  • npx is 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 in package.json and run via npx or npm exec; a tool used once should be run via npx and 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!