dominikb/composer-license-checker
A Composer plugin that checks your project's dependency licenses against configurable allow and deny lists, helping you spot incompatible or risky packages early and keep releases compliant.
Install it as a dev-only tool so it never ships in your production artifact:
composer require --dev dominikb/composer-license-checker
Look at the package's own command list first — it's a small Symfony Console app, so --help tells you everything it supports for your version:
vendor/bin/license-checker --help
vendor/bin/license-checker check --help
Create a policy file in the project root (default name license-checker.php). Start permissive — you want to discover what you already depend on before you start failing builds:
<?php
// license-checker.php
return [
'allow' => [
'MIT',
'Apache-2.0',
'BSD-2-Clause',
'BSD-3-Clause',
'ISC',
],
'deny' => [
'GPL-3.0',
'AGPL-3.0',
],
'exclude' => [
// 'some/internal-package',
],
];
Run the check and read the output. It scans your installed packages (from composer.lock / vendor/composer/installed.json), so composer install must have run first:
vendor/bin/license-checker check
First real use case: turn it into a merge gate. Add a Composer script so everyone runs the same thing:
{
"scripts": {
"licenses": "license-checker check"
}
}
Then composer licenses becomes your team-wide entry point, and the command's non-zero exit code is the CI signal.
Discover-then-tighten workflow. On a legacy codebase, don't write the allow list from memory. Run the checker, dump every license it found, then paste the safe ones into allow and open a ticket for anything ambiguous. Re-run after each dependency bump to catch drift.
Two-tier CI: PR check vs. production artifact check. Dev dependencies often carry copyleft licenses you never distribute. Run the checker twice with different scopes:
# .github/workflows/licenses.yml
- run: composer install --no-interaction --prefer-dist
- run: vendor/bin/license-checker check # full tree incl. dev
- run: vendor/bin/license-checker check --no-dev # what actually ships
The second invocation is the one that matters for distribution obligations; the first keeps the repo honest.
Allow-list over deny-list for enforcement. A deny list only catches licenses you already thought of. For a hard gate, treat allow as the primary mechanism and use deny for explicitly unacceptable licenses you want a loud error on. Everything unknown should fail by default — see the "unknown license" gotcha below.
Gate upgrades, not just new packages. Pair the checker with Dependabot/Renovate. When a transitive bump changes a license (rare, but it happens during major version jumps), the license job fails in the same PR that introduced the change, which is exactly where you want the conversation.
Pre-commit / local guard. Add a short git hook or a Make target so contributors catch a violation before pushing:
# Makefile
.PHONY: licenses
licenses:
vendor/bin/license-checker check
Exclude the exceptions explicitly. Internal/proprietary packages and forks with broken license metadata go in exclude — but leave a comment beside each entry explaining why, or the file rots into a pile of unexplained bypasses.
Inventory reporting. Use the plain listing output (or composer licenses) to produce a human-readable license inventory for compliance reviews, and reserve the check command for the machine gate. Two audiences, two commands.
Programmatic use. It's a normal PHP package, so if you need the data in a custom report you can bootstrap its classes from a script rather than shelling out and re-parsing text. Prefer the CLI until you actually need this.
proprietary, missing a license key, or with unknown will not be covered by your allow list — make sure they fail rather than silently passing, and handle internal packages via exclude, not by widening allow.MIT OR Apache-2.0 or (MIT AND GPL-3.0) is not the string MIT. Verify how your installed version treats compound expressions before you trust a green build; if it doesn't decompose them, either allow the exact expression or exclude the package with a comment.BSD-3-Clause vs BSD 3-Clause vs BSD-3-clause are different strings. Copy IDs verbatim from the tool's output into your config; don't retype them.WORKDIR, or a monorepo subproject can silently load no config and default to "everything passes." Always cd to the project root (or set the path explicitly if the version supports it) and confirm the config was actually loaded on the first run.composer install in CI, and be aware that a stale vendor/ on a developer machine produces stale results. When in doubt, composer install first.vendor/bin/license-checker check || true in a CI script makes the whole exercise decorative. Let non-zero fail the job, and place the step right after install so builds fail fast.allow/deny or allowed/denied, and exclude may be named differently. Check the README shipped with your exact installed version rather than a blog post.^x. Pin it in require-dev, let your bot open PRs, and read the changelog on majors — config schema changes are the most likely breaking change.composer.json/vendor/ depending on where you invoke it. As a dev dependency in require-dev, resolution is unambiguous.composer licenses, don't replace it. Composer's built-in command prints a table and never fails; this tool adds the policy and the exit code. Keep both: the built-in for humans, this one for the gate.How can I help you explore Laravel packages today?