How to check outdated npm packages

Run npm outdated in the project root. It prints one row per package that is behind, with Current, Wanted and Latest columns, and exits 1 when the list is not empty. Wanted is the newest version your semver range allows; Latest is the newest the registry publishes.

Why check this

A tester runs this before a regression cycle is planned, because it decides how much of the suite the cycle has to cover. Three patch bumps inside a range are a smoke run. A framework major is a full pass. The command tells you which of the two you are looking at, in one screen, before anyone estimates.

The failure it prevents is a silent upgrade during the cycle. A range like ^16.4.7 lets a fresh npm install on a build agent pull a version nobody tested, so the build that ships is not the build that passed.

Prerequisites

{
  "name": "outdated-demo",
  "version": "1.0.0",
  "private": true,
  "dependencies": {
    "dayjs": "1.11.13",
    "dotenv": "16.4.7",
    "postcss": "8.4.49"
  }
}

Registry versions move, so a run on a later date returns higher numbers than the ones below, captured on 2026-09-12.

Steps

  1. Step 1.

    Run the command in a real project and read the exit code with it.

    npm outdated; echo "exit=$?"
    
    Package               Current   Wanted   Latest  Location                           Depended by
    @tailwindcss/postcss   4.1.13   4.1.13    4.3.3  node_modules/@tailwindcss/postcss  how2check
    @types/node           22.18.1  22.18.1  22.20.2  node_modules/@types/node           how2check
    @types/react          19.1.13  19.1.13   19.3.0  node_modules/@types/react          how2check
    @types/react-dom       19.1.9   19.1.9   19.3.0  node_modules/@types/react-dom      how2check
    cron-parser             5.4.0    5.4.0   5.10.0  node_modules/cron-parser           how2check
    tailwindcss            4.1.13   4.1.13    4.3.3  node_modules/tailwindcss           how2check
    tsx                    4.20.6   4.20.6  4.23.13  node_modules/tsx                   how2check
    typescript              5.9.2    5.9.2    7.0.2  node_modules/typescript            how2check
    zod                     4.1.8    4.1.8    4.6.2  node_modules/zod                   how2check
    exit=1

    Nine rows from this site's own repository. Wanted equals Current on every one of them, because each is declared as an exact version with no range operator. TypeScript is two majors behind.

  2. Step 2.

    Run it in the demo project, which is also pinned exactly.

    npm outdated
    
    Package  Current   Wanted   Latest  Location              Depended by
    dayjs    1.11.13  1.11.13  1.11.23  node_modules/dayjs    outdated-demo
    dotenv    16.4.7   16.4.7   17.4.2  node_modules/dotenv   outdated-demo
    postcss   8.4.49   8.4.49   8.5.28  node_modules/postcss  outdated-demo

    Same shape as step 1. An exact pin makes Wanted meaningless, because the range admits exactly one version.

  3. Step 3.

    Change the three ranges to carets in package.json and run the command again, without reinstalling.

    npm outdated
    
    Package  Current   Wanted   Latest  Location              Depended by
    dayjs    1.11.13  1.11.23  1.11.23  node_modules/dayjs    outdated-demo
    dotenv    16.4.7   16.6.1   17.4.2  node_modules/dotenv   outdated-demo
    postcss   8.4.49   8.5.28   8.5.28  node_modules/postcss  outdated-demo

    Nothing on disk moved; only the ranges did. The dotenv row now has three different numbers: ^16.4.7 reaches 16.6.1 and stops at the major boundary, while the registry is on 17.4.2.

  4. Step 4.

    Ask for the packages you did not choose.

    npm outdated --all
    
    Package  Current   Wanted   Latest  Location              Depended by
    dayjs    1.11.13  1.11.23  1.11.23  node_modules/dayjs    outdated-demo
    dotenv    16.4.7   16.6.1   17.4.2  node_modules/dotenv   outdated-demo
    nanoid    3.3.19   3.3.19    6.0.1  node_modules/nanoid   postcss
    postcss   8.4.49   8.5.28   8.5.28  node_modules/postcss  outdated-demo

    A fourth row appears. nanoid is three majors behind and reaches the project through postcss, which is why the Depended by column says postcss and not the project name.

How to read the result

| What you see | What it means | What to do | | --- | --- | --- | | Wanted above Current | The lockfile holds a version older than your range allows | A fresh npm install will move it. Run the suite before that happens on a build agent, not after. | | Latest above Wanted | A major release exists outside the range | Plan it as a code change. Read the changelog, not the version number. | | Wanted equals Current on every row | The dependency is pinned exactly | Nothing will move on its own. Every upgrade is a deliberate edit. | | No output at all, exit 0 | Every direct dependency is at Latest | Re-run with --all before you report it; transitive packages are hidden by default. | | A row whose Depended by is another package | A transitive dependency | You cannot bump it directly. Bump its parent, or add an overrides entry. |

Common mistakes

Sign: A CI step that runs npm outdated fails the job, and the log shows a normal-looking table.Cause: The command exits 1 whenever the list is not empty and 0 when it is empty. Both were measured here: nine rows returned 1, a project with nothing behind returned 0 and printed nothing. Under set -e a healthy report stops the pipeline.
Sign: Wanted matches Current on every row, and the project is read as up to date.Cause: It means the ranges are exact pins, not that the packages are current. In the table in step 1 every Latest column is ahead, one of them by two major versions. Wanted only says what your own range would accept.
Sign: The report is empty and the tree still contains a package years behind.Cause: Only direct dependencies are listed by default. Adding --all to the same demo project surfaced nanoid at 3.3.19 against a latest of 6.0.1, pulled in by postcss. Nothing in the default view hinted that the row existed.
Sign: Latest is treated as the highest version number published.Cause: It is the latest dist-tag, which a maintainer sets. On 2026-09-12 the typescript tags were latest 7.0.2, rc 7.0.1-rc and next 7.1.0-dev.20260911.1. Higher versions exist on the registry and npm outdated does not mention them.

What to check next

FAQ

What is the difference between Wanted and Latest?

Wanted is the highest version that satisfies the range in your package.json. Latest is the version the registry serves under the latest dist-tag. With ^16.4.7, Wanted was 16.6.1 and Latest was 17.4.2, because a caret range stops at the next major.

How do I update to the Wanted column?

npm update moves packages to Wanted and rewrites the lockfile. On the demo project npm update --dry-run planned dotenv 16.4.7 to 16.6.1, not to the latest 17.4.2. Reaching Latest means editing the range yourself and running the tests.

Why does npm outdated show nothing?

Either every direct dependency is at Latest, or the ranges are satisfied and you are looking at the default view. Run npm outdated --all to include transitive packages before concluding the tree is current.

Does npm outdated change anything on disk?

No. It reads node_modules and queries the registry. The demo in step 3 changed only the ranges in package.json, and the Current column stayed at the installed versions.

Verified

Verified by Maks Vernynpm 10.9.8Node.js 22.23.2

Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.

basic6 minpublished updated Maks Verny