How to check npm package licenses
Run npx license-checker --summary in the project root. It prints one line per distinct license value with a count. This repository reported 253 packages across 12 values, three of which are not plain identifiers: two SPDX expressions and one UNLICENSED. Read those three by hand.
Why check this
License review happens before a release that ships compiled output, and again whenever a dependency upgrade adds names to the tree. The whole check is a list of strings, which is why it is usually automated badly.
The failure it prevents is a copyleft term arriving inside an expression. @img/sharp-wasm32 here is Apache-2.0 AND LGPL-3.0-or-later AND MIT. A reviewer scanning a summary sees the familiar words at both ends and moves on, and the allowlist step below waves the package through even when LGPL is not on the list.
Prerequisites
- Node 22 and npm 10. Outputs below came from license-checker 25.0.1 on npm 10.9.8 and Node 22.23.2.
- An installed node_modules. license-checker reads the packages on disk and names a
licenseFilepath for each, so a tree that was never installed produces nothing. - The license-checker README for the flag names.
- A four-package fixture for the allowlist steps, so the answers can be checked by hand. Three vendored manifests and a root:
{
"name": "licdemo", "version": "1.0.0", "private": true, "license": "MIT",
"dependencies": {
"dual-licensed": "file:vendor/dual-licensed",
"either": "file:vendor/either",
"no-license": "file:vendor/no-license"
}
}
{ "name": "dual-licensed", "version": "1.0.0", "license": "Apache-2.0 AND LGPL-3.0-or-later" }
{ "name": "either", "version": "1.0.0", "license": "(MIT OR GPL-3.0-only)" }
{ "name": "no-license", "version": "1.0.0" }
Put each vendored manifest in vendor/<name>/package.json and run npm install.
Steps
- Step 1.
Count the license values in the project you care about.
npx --yes license-checker@25.0.1 --summary 2>/dev/null├─ MIT: 212 ├─ ISC: 11 ├─ Apache-2.0: 10 ├─ BSD-3-Clause: 5 ├─ MPL-2.0: 5 ├─ BlueOak-1.0.0: 4 ├─ Apache-2.0 AND LGPL-3.0-or-later AND MIT: 1 ├─ Apache-2.0 AND LGPL-3.0-or-later: 1 ├─ CC-BY-4.0: 1 ├─ BSD-2-Clause: 1 ├─ UNLICENSED: 1 └─ 0BSD: 1Twelve values over 253 packages. Two of the twelve are SPDX expressions rather than identifiers, and one is the placeholder npm uses for a package that publishes no license.
- Step 2.
Pull out every value that is not a single identifier, and name the package behind it.
npx --yes license-checker@25.0.1 --json 2>/dev/null | node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{for(const [k,v] of Object.entries(JSON.parse(s))){const L=String(v.licenses);if(/ AND | OR |UNKNOWN|UNLICENSED/.test(L))console.log(L+' '+k);}})"Apache-2.0 AND LGPL-3.0-or-later AND MIT @img/sharp-wasm32@0.35.4 Apache-2.0 AND LGPL-3.0-or-later @img/sharp-win32-x64@0.35.4 UNLICENSED h2check@0.1.0Three rows out of 253. The last one is the repository itself, which the report includes alongside its dependencies.
- Step 3.
Switch to the fixture and read the four shapes a license field can take.
npx --yes license-checker@25.0.1 2>/dev/null | grep -E "^├─|^└─|licenses:"├─ dual-licensed@1.0.0 │ ├─ licenses: Apache-2.0 AND LGPL-3.0-or-later ├─ either@1.0.0 │ ├─ licenses: (MIT OR GPL-3.0-only) ├─ licdemo@1.0.0 │ ├─ licenses: UNLICENSED └─ no-license@1.0.0 ├─ licenses: UNKNOWNno-licensehas nolicensefield at all and becomesUNKNOWN.licdemodeclares"license": "MIT"and is reported asUNLICENSED, because its manifest also sets"private": true. - Step 4.
Test the allowlist flag against the AND expression, one term at a time.
for a in "Apache-2.0" "LGPL-3.0-or-later" "MIT"; do npx --yes license-checker@25.0.1 --onlyAllow "$a" --excludePackages "licdemo@1.0.0;no-license@1.0.0;either@1.0.0" >/dev/null 2>&1; echo "$a -> exit $?"; doneApache-2.0 -> exit 0 LGPL-3.0-or-later -> exit 0 MIT -> exit 1dual-licensedisApache-2.0 AND LGPL-3.0-or-later. Allowing either term on its own lets it pass. The flag readsANDthe way it readsOR. - Step 5.
Repeat the same test on the real tree, with a list an audit would plausibly use.
npx --yes license-checker@25.0.1 --onlyAllow "MIT;ISC;Apache-2.0;BSD-2-Clause;BSD-3-Clause;0BSD;BlueOak-1.0.0;CC-BY-4.0;MPL-2.0;UNLICENSED" >/dev/null 2>&1; echo "LGPL-3.0-or-later not on the list: exit $?"LGPL-3.0-or-later not on the list: exit 0The list permits ten identifiers and LGPL is not among them. Two packages carrying it passed. Dropping
MPL-2.0from the same list did produce exit 1, naminglightningcss-win32-x64-msvc@1.30.1, so the flag works on plain identifiers.
How to read the result
| What you see | What it means | What to do |
| --- | --- | --- |
| A plain identifier such as MIT | One SPDX license, matched by string comparison | Compare it against your list and move on. |
| A AND B | Both sets of terms apply to the package | Review every term. --onlyAllow passes it on any one of them. |
| (A OR B) | The package offers a choice and you take one | Record which one you chose, because the report will not. |
| UNKNOWN | The manifest has no license field | Open the package directory and look for a LICENSE file before assuming anything. |
| UNLICENSED | The manifest sets private: true, or declares that value | Exclude your own packages with --excludePrivatePackages. |
Common mistakes
What to check next
- How to check dependency tree npm: the tree the report walks, including the copies installed twice.
- How to check why a package is installed: names the dependency that pulled in a license you cannot accept.
- How to check unused dependencies in package json: removing a package is one way to close a license finding.
- How to check npm packages for vulnerabilities: the other report read over the same installed tree.
FAQ
How do I check npm package licenses?
npx license-checker --summary for the counts, then --json for the rows behind any value you do not recognise. Run it after an install, because it reads node_modules rather than the registry.
What does UNKNOWN mean in the report?
The package manifest has no license field. It is not a statement that the code is free to use. Open the package directory and look for a LICENSE file, then ask the publisher if there is none.
Can I fail a build on a forbidden license?
--onlyAllow returns exit 1 on a plain identifier that is not on the list. It returns 0 for an SPDX expression whose terms are only partly allowed, so pair it with a pass over the JSON output.
Why is my own project in the list?
The root package is reported with its dependencies. --excludePrivatePackages removed it from the fixture report here, and --excludePackages "name@version" removes any named package.
Verified
Verified by Maks Vernylicense-checker 25.0.1npm 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.
Related on this site
intermediate8 minpublished updated Maks Verny