How to check installed npm package version

Run npm ls with the package name in the project root. It prints the installed version under every package that depends on it, and exits 1 when nothing matches. npm pkg get returns the declared range instead of a version, and node -p on the installed manifest returns what the runtime loads.

Why check this

A tester runs this the moment a bug report names a library version. The question "is the fix in the build I am testing" has one honest answer and three commands that look like they give it, so the first job is picking the command that reports the tree on disk rather than the intention in package.json.

The failure it prevents is closing a defect against the wrong copy. A dependency can be installed twice at two versions in one project, and the version a reviewer reads from the project root is not always the one the failing code path loads.

Prerequisites

{
  "dependencies": {
    "next": "^16.3.5"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "4.1.13",
    "typescript": "5.9.2",
    "vitest": "^5.0.0"
  }
}

Steps

  1. Step 1.

    Ask for one direct dependency.

    npm ls typescript; echo "exit=$?"
    
    h2check@0.1.0 D:\how2check
    `-- typescript@5.9.2
    
    exit=0

    One line under the project, one version, exit 0. This is the answer whenever the package appears once in the tree.

  2. Step 2.

    Ask for a package that is not installed. The text and the exit code disagree about how serious this is.

    npm ls express; echo "exit=$?"
    
    h2check@0.1.0 D:\how2check
    `-- (empty)
    
    exit=1

    (empty) is the answer to "is this package installed", and the answer is no. Exit 1 here reports an absent package, not a command that failed to run.

  3. Step 3.

    Ask for a package that several dependencies want.

    npm ls postcss
    
    h2check@0.1.0 D:\how2check
    +-- @tailwindcss/postcss@4.1.13
    | `-- postcss@8.5.28
    +-- next@16.3.5
    | `-- postcss@8.5.23
    `-- vitest@5.0.0
    `-- vite@8.3.0
      `-- postcss@8.5.28 deduped

    Three rows and two versions. next declares postcss exactly at 8.5.23, so it gets a private copy. The deduped marker on the third row means vite shares the 8.5.28 already installed for @tailwindcss/postcss, not that a third copy exists.

  4. Step 4.

    Compare that against the two shortcuts people reach for first.

    npm pkg get dependencies.postcss devDependencies.postcss && node -p "require('postcss/package.json').version"
    
    {}
    8.5.28

    npm pkg get reads package.json, and postcss is not declared there, so it returns an empty object. require resolves from the project root and finds the hoisted copy, 8.5.28. Neither command knows about 8.5.23.

  5. Step 5.

    Read the version the dependent actually loads.

    node -p "require('./node_modules/next/node_modules/postcss/package.json').version"
    
    8.5.23

    Code inside next resolves postcss to its nested copy before it reaches the hoisted one. A defect in 8.5.23 is present in this project while the root reports 8.5.28.

  6. Step 6.

    Get the path of every copy, with the range each dependent asked for.

    npm explain postcss
    
    postcss@8.5.23
    node_modules/next/node_modules/postcss
    postcss@"8.5.23" from next@16.3.5
    node_modules/next
      next@"^16.3.5" from the root project
    
    postcss@8.5.28 dev
    node_modules/postcss
    postcss@"^8.4.41" from @tailwindcss/postcss@4.1.13
    node_modules/@tailwindcss/postcss
      dev @tailwindcss/postcss@"4.1.13" from the root project
    postcss@"^8.5.28" from vite@8.3.0
    …

    Two versions, two directories, and the reason for each. The exact pin postcss@"8.5.23" is what forced the second copy; the two caret ranges both accept 8.5.28 and share one.

How to read the result

| What you see | What it means | What to do | | --- | --- | --- | | One row, one version | The package is installed once | Quote that version in the bug report. | | Two rows with different versions | Two copies are installed at different paths | Run npm explain and name the path that the failing code resolves from. | | A row ending in deduped | That dependent shares a copy listed earlier | Count it once. It is one directory on disk, not two. | | (empty) with exit 1 | Nothing in the tree matches the name | Confirm the spelling, then check whether it is installed globally. | | npm pkg get returning {} | The name is not declared in package.json | It arrived through another package. npm ls still finds it. | | An invalid or extraneous marker | The tree does not match the lockfile | Reinstall before you trust any version on this page. |

Common mistakes

Sign: npm pkg get is quoted as the installed version.Cause: It reads package.json and returns the declared range. In this repository npm pkg get dependencies.next returns the string ^16.3.5, which is not a version and is not what is on disk. For a package nobody declared, such as postcss here, it returns an empty object.
Sign: A single node -p require call is used to settle which version is installed.Cause: It resolves from the directory you are standing in and finds the hoisted copy. From the project root that answered 8.5.28; from inside next the same package resolves to 8.5.23. Both copies were installed and both are loaded at runtime.
Sign: The deduped rows are counted as extra installations.Cause: In the step 3 output postcss@8.5.28 appears twice, once under @tailwindcss/postcss and once under vite with the deduped marker. That is one directory, node_modules/postcss, shared by both. npm explain confirms it by printing one path for the two ranges.
Sign: npm view is used instead of npm ls.Cause: npm view postcss version queries the registry and returned 8.5.28 here, which happened to match one of the two installed copies and told you nothing about either. It reports what is published, and it works in a directory with no project in it at all.

What to check next

FAQ

How do I check if a package is installed with npm?

npm ls with the package name. It exits 0 and prints the version when the package is in the tree, and exits 1 and prints (empty) when it is not. Read the exit code, because both cases print the project line above it.

What is the difference between npm ls and npm view?

npm ls reads node_modules in the current project. npm view queries the registry and reports what is published under the latest dist-tag. The second one answers a question about the internet, not about your build.

Why does npm ls print two versions of one package?

Dependents asked for ranges that no single version satisfies. Here next pinned postcss to 8.5.23 while two other packages accepted 8.5.28, so npm installed both, one at the root and one nested under next.

Which version does my own code load?

The one node -p "require('the-name/package.json').version" prints when run from your project root, because your code resolves from the same place. Code inside a dependency can resolve to a different copy.

Does npm ls read the lockfile?

No, it reads the installed tree. An invalid or extraneous marker in its output is npm saying the two disagree, which is a separate finding from the version itself.

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.

basic5 minpublished updated Maks Verny