How to check unused dependencies in package json

Run npx depcheck in the project root. It lists every package in package.json that no file imports. In the project below it named three, and one of them is genuinely unused: dotenv is loaded by a node flag and postcss-nested by a config key, and depcheck reads neither.

Why check this

Dependency cleanup runs before a release, or whenever an audit report is long enough that somebody proposes deleting entries. The tool that produces the delete list is the thing to verify first.

The failure it prevents is a deploy that dies at process start. Remove dotenv on the tool's word and node -r dotenv/config src/index.js throws Cannot find module before a single line of application code runs. Local tests that call functions directly never touch that script, so the pipeline stays green until the service starts.

Prerequisites

{
  "name": "depdemo",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "start": "node -r dotenv/config src/index.js",
    "build": "node build.js"
  },
  "dependencies": {
    "dayjs": "1.11.13",
    "dotenv": "16.4.7",
    "lodash": "4.17.21",
    "postcss": "8.4.49",
    "postcss-nested": "7.0.2"
  }
}
// build.js
const fs = require('fs');
const postcss = require('postcss');
const config = require('./postcss.config.cjs');

const plugins = Object.keys(config.plugins).map((name) => require(name)(config.plugins[name]));
const css = fs.readFileSync('src/app.css', 'utf8');

postcss(plugins)
  .process(css, { from: 'src/app.css', to: 'dist/app.css' })
  .then((result) => {
    fs.writeFileSync('dist/app.css', result.css);
    console.log(result.css);
  });
// postcss.config.cjs
module.exports = {
  plugins: {
    'postcss-nested': {},
  },
};
// src/index.js
const dayjs = require('dayjs');
console.log(process.env.STAMP_LABEL, dayjs().format('YYYY-MM-DD'));
// src/log.js
const pc = require('picocolors');
module.exports = (msg) => console.log(pc.green(msg));
/* src/app.css */
.card { color: #111; .title { font-weight: 700; } }

One more file, .env, holds the single line STAMP_LABEL=nightly. Run npm install, then mkdir dist.

Steps

  1. Step 1.

    Prove the build uses the two packages that are about to be called unused.

    npm start && npm run build
    
    > depdemo@1.0.0 start
    > node -r dotenv/config src/index.js
    
    nightly 2026-09-12
    
    > depdemo@1.0.0 build
    > node build.js
    
    .card { color: #111; } .card .title { font-weight: 700; }

    nightly came from .env, so dotenv ran. The nested .title rule was flattened into .card .title, so postcss-nested ran.

  2. Step 2.

    Run the tool.

    npx --yes depcheck@1.4.7
    
    Unused dependencies
    * dotenv
    * lodash
    * postcss-nested
    Missing dependencies
    * picocolors: .\src\log.js

    Three names in the first list, one of which step 1 measured as unused: lodash. The second list is the more interesting half, and it is the one people skip.

  3. Step 3.

    Read the exit code before wiring this into a pipeline.

    npx --yes depcheck@1.4.7 >/dev/null 2>&1; echo "depcheck exit $?"
    
    depcheck exit 127

    127 with three findings, 127 with one, and 0 when nothing is flagged. The code does not count anything.

  4. Step 4.

    Ask which file proved each package used, rather than trusting the summary.

    npx --yes depcheck@1.4.7 --json | node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{const j=JSON.parse(s);console.log('using:',Object.keys(j.using).join(', '));console.log('unused:',j.dependencies.join(', '));console.log('missing:',Object.keys(j.missing).join(', '));})"
    
    using: postcss, dayjs, picocolors
    unused: dotenv, lodash, postcss-nested
    missing: picocolors

    using holds only the three names reached through a require call. Nothing in the JSON records a package script or a config key, so nothing there could have found the other two.

  5. Step 5.

    Silence the two false positives and see what is left.

    npx --yes depcheck@1.4.7 --ignores="dotenv,postcss-nested"
    
    Unused dependencies
    * lodash
    Missing dependencies
    * picocolors: .\src\log.js

    One package to delete and one to declare. That is the real result of the check, and it took step 1 to get here.

How to read the result

| What you see | What it means | What to do | | --- | --- | --- | | A name under Unused dependencies | No file in the scanned tree imports it | Search the repository for the name as a plain string before deleting it. | | A name under Missing dependencies with a file | That file imports a package package.json does not declare | Declare it. It resolves today only because something else installed it. | | No depcheck issue | Both lists are empty for the current flags | Confirm the --ignores list is still justified. | | Exit code 127 | At least one finding exists | Read the JSON. The code is the same for one finding and for twenty. |

Common mistakes

Sign: A package the build needs is listed as unused.Cause: depcheck resolves imports. A dependency named only as a key in a config file is invisible to it. postcss-nested appears in this project solely as the string 'postcss-nested' in postcss.config.cjs, and step 1 shows the build using it. The README lists special parsers for several config formats and this case is not among them.
Sign: A package loaded by an npm script flag is listed as unused.Cause: node -r dotenv/config is a runtime flag, not an import, so no source file mentions dotenv. The start script printed a value that only exists because dotenv parsed .env, and depcheck still named it. Grep the scripts block before acting on the list.
Sign: A CI step running depcheck is treated as a broken tool because it exited 127.Cause: 127 is the shell code for command not found, and it is also what this version returns whenever it has a finding. The two cases are indistinguishable from the exit status. Run with --json and decide on the arrays.
Sign: Only the unused list is reviewed.Cause: picocolors here is required by src/log.js and declared by nobody. It resolves because postcss depends on it, so the import works until postcss changes its own dependencies. That is a build that breaks on an unrelated upgrade.

What to check next

FAQ

How do I check unused dependencies?

npx depcheck in the project root. Treat its output as candidates. Confirm each one by searching the repository for the package name as a plain string, including package scripts and config files.

Does it check devDependencies too?

Yes, under a separate devDependencies heading. The project above declares none, so that section was empty in every run on this page.

How do I suppress a name I have confirmed?

--ignores="dotenv,postcss-nested", comma separated. Keep the reason next to the flag in your pipeline file, because the next reader cannot tell a confirmed exception from an old one.

Can I fail a build on the result?

Run it with --json and decide on the dependencies and missing keys. The exit code is 127 for any finding and 0 for none, which is too coarse and collides with the shell code for a missing command.

Verified

Verified by Maks Vernydepcheck 1.4.7npm 10.9.8Node 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.

intermediate8 minpublished updated Maks Verny