Why is this package installed?
node_modules holds many more packages than package.json lists: installing express 4 alone adds more than sixty. Each package manager can print how one of them got there — which package requires it, which package requires that one, back to your project.
npm
npm why is another name for npm explain. It prints each installed copy of the package and, under it, the package that requires it and the range it asks for, then the package that requires that one, until the chain reaches the root project:
$ npm why ms
[email protected]
node_modules/ms
ms@"2.0.0" from [email protected]
node_modules/debug
debug@"2.6.9" from [email protected]
node_modules/body-parser
body-parser@"~1.20.5" from [email protected]
node_modules/express
express@"^4.22.3" from the root project
debug@"2.6.9" from [email protected]
node_modules/express
express@"^4.22.3" from the root project
…
[email protected]
node_modules/send/node_modules/ms
ms@"2.1.3" from [email protected]
node_modules/send
send@"~0.19.0" from [email protected]
node_modules/express
express@"^4.22.3" from the root project
…
Here ms is installed twice: 2.0.0 for debug, 2.1.3 for send. --json prints the same chains as JSON, and --workspace <name> only those of one workspace.
pnpm
pnpm why prints a reverse tree: the package at the top, the packages that depend on it below, down to your project. [deduped] marks a package already shown.
$ pnpm why ms
[email protected]
└─┬ [email protected]
├─┬ [email protected]
│ └─┬ [email protected]
│ └── [email protected] (dependencies)
├── [email protected] [deduped]
…
[email protected]
└─┬ [email protected]
├─┬ [email protected]
│ └── [email protected] (dependencies)
└─┬ [email protected]
└── [email protected] [deduped]
Found 2 versions of ms
-r looks through every project in a workspace, --prod and --dev limit the search to one kind of dependency, and --json prints JSON.
Yarn and Bun
Yarn 1 prints, for each copy, the reason it exists:
$ yarn why ms
…
info => Found "[email protected]"
info Reasons this module exists
- "express#debug" depends on it
- Hoisted from "express#debug#ms"
…
info => Found "send#[email protected]"
info This module exists because "express#send" depends on it.
Yarn 4's yarn why ms lists only the packages that depend on ms directly. With -R it prints every path from the project down to it:
$ yarn why ms -R
└─ my-app@workspace:.
└─ express@npm:4.22.3 (via npm:4)
├─ body-parser@npm:1.20.8 (via npm:~1.20.5)
│ └─ debug@npm:2.6.9 [6e765] (via npm:2.6.9 [6e765])
│ └─ ms@npm:2.0.0 (via npm:2.0.0)
…
├─ send@npm:0.19.2 (via npm:~0.19.0)
│ ├─ debug@npm:2.6.9 [6e765] (via npm:2.6.9 [470d2])
│ └─ ms@npm:2.1.3 (via npm:2.1.3)
…
Bun has had bun why since 1.2.19.
A dependency you added yourself
$ npm why express
[email protected]
node_modules/express
express@"^4.22.3" from the root project
For a package in your own package.json, the chain is one step long: the project asks for it. pnpm ends at [email protected] (dependencies), and Yarn 1 says This module exists because it's specified in "dependencies". That is all a package manager can know. Why express was chosen, why it stays on 4, and what to run before moving it were never written anywhere it reads.
Pacmon keeps that beside package.json, in .pacmon/DEPENDENCY-NOTES.md, and shows it on the dependency's line:
## express
HTTP API layer (SEC-1234).
Do not upgrade to v5 — the auth middleware is incompatible.
### Agent notes
- purpose: HTTP framework; serves the public REST API and the webhook receiver
- constraint: stay on ^4 — v5 changes router path matching and the session API
- verify: `vitest src/api` and the login e2e (`pnpm e2e:auth`)
After npm audit
When npm audit reports a vulnerable package deep in the tree, npm why names the dependency of yours that brings it in. Moving that dependency is where its note matters: constraint: says what must not change, and verify: what to run afterwards. Dependency notes for JavaScript covers the rest.