npm outdated vs npm-check-updates
Two tools that both show you newer package versions, but with very different opinions about what to do next.
Dependency maintenance has been part of the Node.js workflow since npm popularised the package.json manifest in the early 2010s. Two tools remain the most common way to find out when a dependency is behind: npm outdated, which has been part of the npm CLI since version 1.x, and npm-check-updates (often called ncu), a community package first published in 2014. They look similar on the surface, but they answer different questions and have different effects on your project.
What npm outdated shows
Run npm outdated in any project and you get a table of every direct dependency with four columns: Current (the version installed in node_modules), Wanted (the highest version that satisfies the semver range in your package.json), Latest (the version tagged as 'latest' in the registry), and Location (where in the dependency tree it lives).
Package Current Wanted Latest
express 4.18.2 4.19.2 5.0.0
lodash 4.17.20 4.17.21 4.17.21
mocha 9.0.0 9.0.3 10.7.0The colour coding is informative: yellow means a newer version exists above your semver range (usually a major bump), and red means a newer version within your range is available. The command does exactly one thing: it reads the registry, compares versions, and prints the result. It never touches a file.
What npm outdated leaves untouched
This surprises some developers. npm outdated is purely a reporting command. It does not update package.json, it does not change node_modules, and it does not regenerate the lockfile. The closest npm provides for automatic updates is npm update, which installs the wanted version for each package but also does not modify your package.json unless you pass --save (and even then it respects your existing range).
This constraint is by design. npm's philosophy is that the range in your manifest is a declaration of intent. Changing it is a semver-aware decision that should be made deliberately, not swept along by a bulk command.
What npm-check-updates adds
ncu (the CLI shorthand for npm-check-updates) solves a different problem. Where npm outdated says "here is what you could update to", ncu says "here is what exists, and ncu -u will write the new ranges into your package.json right now." The key distinction is the -u (or --upgrade) flag. Without it, ncu is a read-only report similar to npm outdated. With -u, it overwrites your manifest.
$ ncu
eslint ^7.32.0 → ^8.56.0
prettier ^2.8.0 → ^3.3.0
$ ncu -u
Upgrading package.json
eslint ^7.32.0 → ^8.56.0
prettier ^2.8.0 → ^3.3.0
$ npm install # now install the new rangesncu -u changes the version ranges in your package.json. It does not install anything. The manual says it clearly: after running ncu -u, you must run npm install (or your package manager's equivalent) to update the lockfile and node_modules. This separation is intentional and is the same separation that npm outdated enforces.
The core difference: read vs write
npm outdated will never edit a file. It is a diagnostic tool that belongs in CI pipelines, pre-commit hooks, and manual review workflows. ncu -u is an editing tool. It rewrites your package.json to target the latest versions, preserving range operators like ^ and ~ and replacing less-than range expressions with caret equivalents.
Installed, Wanted, and Latest explained
The confusion between these two tools often comes from the version columns. In npm outdated, Current is the exact version physically present in node_modules. Wanted satisfies your current package.json range. Latest is the dist-tag version from the registry with no constraint from your range. ncu collapses this into two columns: the current range from your package.json and the proposed new range. Both tools display the same underlying registry data; ncu just formats it as a diff you can apply. The 'wanted' column in npm outdated is essentially the highest version npm update would install without changing your manifest. The 'latest' column is what ncu considers by default when it proposes a new range.
One subtlety: a dist-tag like 'latest' is not guaranteed to be the highest version number. Package maintainers can tag any version as 'latest' or use alternative tags such as 'next' or 'beta'. ncu respects dist-tags by default but can target the highest version number with --target greatest or a specific tag with --target @next. npm outdated always reports the 'latest' dist-tag in its third column, which may be lower than the 'wanted' column if the maintainer has deliberately held back a tag update.
Lockfile effects and package manager flags
Neither npm outdated nor ncu modifies the lockfile. The lockfile changes only when you run npm install after a manifest change. This is true regardless of which package manager you use. ncu supports npm, yarn, pnpm, deno, and bun via the --packageManager flag (or detects the lockfile automatically). When targeting pnpm, for example, ncu -u still rewrites package.json and you follow up with pnpm install. The separation of concerns is deliberate: one tool manages the declaration, another resolves the dependency graph.
Key flags deserve attention. For npm update: --save propagates version bumps to package.json (disabled by default). The default behaviour installs only the 'wanted' version within your existing range, meaning no manifest change occurs at all. For ncu: --target minor or --target patch restricts upgrades to safe levels, --peer checks peer dependency compatibility, --filter limits which packages are considered, and --cooldown skips versions published too recently. The --doctor flag takes this further by iteratively installing each upgrade and running tests to isolate breaking changes automatically.
A safe workflow combining both tools
A practical routine uses npm outdated (or ncu without -u) for discovery, then a deliberate editing step. Here is one approach:
npm outdated or ncu to see which packages have newer versions. Use npm outdated --long to see dependency types.2. DecideChoose a target policy. Want only safe upgrades? Run ncu --target minor -u. Ready for everything? ncu -u.3. InstallRun npm install (or pnpm install, yarn install) to update the lockfile and node_modules.4. VerifyRun your test suite. Review the diff. Roll back any update that breaks compatibility.For maximum safety in CI, npm outdated --all --json provides machine-readable output you can pipe into a reporting step without granting file write permissions. ncu --errorLevel 2 exits with code 0 when no updates are needed, making it suitable for scheduled checks.
A short history
npm outdated shipped with npm 1.x and has had a stable interface since the early npm 3 releases (circa 2015). npm-check-updates was created by Raine Revere and first published to npm in September 2014. It gained traction because npm itself offered no built-in way to bump version ranges in package.json. Over a decade, ncu has grown from a simple wrapper around the npm registry API to a mature tool with interactive mode, doctor mode (which runs tests to detect breaking upgrades), cooldown gates, peer dependency resolution, and multi-package-manager support. The npm CLI, while stable, has never added a first-class "update the manifest range" command, leaving ncu as the de facto standard for that task.
Sources and further reading
- npm CLI docs: npm outdated — official reference for output columns, colour coding, and configuration flags.
- npm CLI docs: npm update — the companion command that installs wanted versions and the
--saveflag behaviour. - npm-check-updates repository — source code, full CLI reference, interactive mode docs, and config file examples.
- npm-check-updates on npm — package page with version history, dependents, and installation instructions.
- Semantic Versioning 2.0 — the specification that defines the range resolution both tools rely on.