Each lockfile is rebuilt into a dependency graph. For npm, the node_modules path of every entry encodes where it was installed, and a dependency is resolved the same way Node does it - look in your own node_modules first, then walk up towards the root. For pnpm the graph is already flat: every installed instance is keyed by name and version, and its dependencies point at other keys. The two graphs are then compared by package name, every version jump is graded as major, minor, patch or prerelease, and for each change the tool runs a breadth-first search from the root to find the shortest chains that lead to that package. The first hop on that chain whose version differs between the two lockfiles is the one that pulled the change in.
major bump marks an upgrade whose first version number changed - the only level where breaking changes are allowed by convention, so it deserves a look even when it is transitive. new transitive marks a package that appeared without anyone declaring it in package.json; it arrived through another dependency. N versions installed means the resolver could not satisfy every range with one copy, so the same package is installed more than once - usually harmless, occasionally the cause of duplicated state or instanceof surprises. source changed means the resolved tarball now comes from a different registry host, which is worth verifying before merging.
Lockfiles routinely contain private registry URLs and internal package names. Parsing, comparison and chain tracing all run in your browser - nothing is uploaded, stored or sent to analytics. You can verify it by opening the network tab while pasting.
Common questions and answers about this topic.
Paste the older lockfile on the left and the newer one on the right. In a pull request, open the file at the base branch and at the head branch (or run git show main:package-lock.json and git show HEAD:package-lock.json). After a local install that changed the lockfile, git show HEAD:package-lock.json gives you the previous version and the working copy is the new one. Both sides must be the same kind of lockfile - npm with npm, pnpm with pnpm.
A direct dependency is one your package.json declares. A transitive dependency is anything else in the lockfile - it is there because a direct dependency, or one of its dependencies, needs it. Most of a lockfile is transitive, which is why a one-line change to package.json can produce hundreds of changed lines. The direct chip in the table marks packages you declared yourself; everything without it arrived through a chain, and the chain view shows which one.
npm package-lock.json with lockfileVersion 1, 2 and 3, and pnpm-lock.yaml with lockfileVersion 5, 6 and 9. npm-shrinkwrap.json uses the same format as package-lock.json and works too. yarn.lock (both the classic v1 text format and the Berry YAML format) is detected but not supported yet, and the tool says so instead of guessing. Note that npm lockfileVersion 1 has no root dependency list, so for those files the direct label is inferred from top-level position rather than read from package.json.
git diff shows lines, not packages: one package upgrade touches its version, resolved and integrity lines plus every entry that referenced it, so the same change is scattered across the file. GitHub's dependency view lists added, removed and changed packages, but only on GitHub and only as a flat list - it does not tell you that minimatch jumped a major because rimraf was upgraded. This tool works on any two lockfiles you can paste, groups changes by package, grades the jump, and shows the chain that explains it.
For each changed package it searches the newer lockfile (or the older one, for removals) from the root outwards and keeps the shortest chains that reach the package. It then walks the shortest chain from the root and stops at the first hop whose version is different in the other lockfile - that hop is reported as the cause, for example 'driven by rimraf moving from 3.0.2 to 6.0.1'. If the package is declared directly, the cause is package.json itself. If no hop changed at all, the package most likely moved because the resolver re-resolved or deduplicated the tree, and the tool says so rather than inventing a cause.
Not by itself. When two dependencies require ranges that no single version satisfies, the package manager installs both and nests one of them; the lockfile records each copy separately and this tool lists them as N versions installed. That is normal and usually harmless. It becomes worth fixing when the package holds shared state or exports classes checked with instanceof, or when the duplicate is large enough to matter for bundle size. Consult the chains to see which dependency insists on the older range.