composer-normalize: a canonical composer.json, checked in CI
🎵 Provides a composer plugin for normalizing composer.json.
At a glance
- What is it?
- composer-normalize is a Composer plugin that rewrites composer.json into a canonical order and format, so that a manifest diff shows a real dependency change rather than a rearrangement. It can run as a plugin, as a Phar, or through Phive, and its --dry-run mode exits non-zero so continuous integration can fail on an unnormalized file.
- Who is it for?
- Adopt composer-normalize if you review PHP pull requests and want composer.json diffs to show only what changed, and enforce it in CI with composer normalize --dry-run so an unnormalized manifest fails the build. Do not adopt it if your project has deliberately hand-arranged composer.json, since the normalizer reorders sections and moves blocks, and do not expect it to reorder anything other than composer.json itself.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem is not the JSON, it is the diff
The README states the choice in the plainest possible terms. When it comes to formatting composer.json, you can format it manually and request changes when contributors format it differently, you can stop caring, or you can use ergebnis/composer-normalize, which normalizes composer.json so you do not have to.
That framing is about review noise rather than aesthetics. A composer.json is a long file with many top-level sections, and two people adding a dependency in different places produce a diff that mixes a real change with a rearrangement. Once that happens in a few pull requests, the file stops being reviewable, and the cost lands on every reviewer rather than on the author of the mess.
The tool is a Composer plugin, which means it hooks into the Composer command set and adds a normalize command. It is published on Packagist under ergebnis/composer-normalize, and the README links a blog post from 2018 explaining the reasoning in more detail, alongside an examples section that shows a real diff.
The example uses the composer.json of pestphp/pest at version 0.3.19, and the diff shown is large: a require-dev block moved, a config block and an extra block reordered, and the autoload section reshuffled. That is the point of the example, since it shows exactly the churn the tool is meant to absorb once, so that it never appears again in a review.
Three ways to install, and one extra config line
The Composer route is the primary one. The plugin is installed as a development dependency:
composer require --dev ergebnis/composer-normalizeand then it has to be allowed to run, because Composer added an allow-plugins layer as an extra security step:
composer config allow-plugins.ergebnis/composer-normalize trueThe README links the Composer pull request that introduced allow-plugins and the Composer configuration documentation for it, so the mechanism is documented upstream rather than being a quirk of this package.
The second route is a Phar. You download the latest composer-normalize.phar from the releases page and make it executable:
chmod +x composer-normalize.pharThe third is Phive, which installs it as a tool:
phive install ergebnis/composer-normalizeAll three are useful for different reasons. The plugin route is the one a project commits to, so that every contributor and every CI run has the same version. The Phar is the one for a machine that should not have the plugin allowed, and the Phive route keeps the tool in a tools directory under version control.
One detail is worth noticing in the repository listing: a phar/ directory and a box.json, which is the configuration for building the Phar, so the binary distribution is built from the same tree rather than assembled by hand.
What the normalize command does, step by step
The Details section of the README lists the behaviour of the NormalizeCommand provided by the NormalizePlugin, and the list is a good description of what a careful formatter does.
It determines whether a composer.json exists. It determines whether a composer.lock exists and, if so, whether it is up to date, unless the --no-check-lock option is used. It uses the ComposerJsonNormalizer from the ergebnis/json-normalizer package to normalize the content of composer.json. It formats the normalized content, either as sniffed or as specified with the --indent-size and --indent-style options. It writes the normalized and formatted content back to the file. And it updates the hash in composer.lock if that file exists and if an update is necessary.
That last point is the one that makes this a plugin rather than a script. Because composer.lock carries a content hash of composer.json, rewriting the manifest invalidates the lock file, and a tool that left the lock stale would break the next composer install. The command also takes an optional file argument, a path to a composer.json file, which defaults to composer.json in the working directory.
The lock handling has two switches of its own. --no-check-lock skips the up-to-date check, and --no-update-lock stops the command from touching the lock file, which is the setting to reach for in a repository where the lock is not committed.
Indent style can be sniffed, or pinned in extra
Formatting can be left to the tool, or specified. The --indent-size option takes an integer greater than 0 and the --indent-style option takes one of space or tab, and the README says each should be used with the other.
The alternative to the command line is configuration in the extra section of composer.json:
{
"extra": {
"composer-normalize": {
"indent-size": 2,
"indent-style": "space"
}
}
}The precedence rule is the part to get right, and the README is explicit: the configuration provided in composer extra always overrides the configuration provided via command line options. That ordering is a deliberate design choice, since a project that has decided on two-space indentation wants that decision to hold even when somebody runs the tool with different flags, and it also means the flags are for one-off use rather than for the project default.
The README also mentions the --diff option, which shows the results of normalizing, and --dry-run, which shows the results without modifying any files. In a review workflow, --diff is the command to run before committing, so you can see the rearrangement before you accept it.
The CI pattern is one command and an exit code
For continuous integration, the README recommends the --dry-run option:
composer normalize --dry-runIn case composer.json is not normalized, or composer.lock is not up to date, the command fails with an exit code of 1 and shows a diff.
That is a well-shaped check. It does not modify the working tree, so a CI failure cannot leave a repository dirty, and it produces a diff in the log, so the person who broke the convention sees what to change rather than just a red build. The second condition, the lock file being out of date, catches a case that is easy to miss: a contributor who edited composer.json by hand and never ran composer update, leaving the content hash stale.
A project that wants the check but not the rewriting installs the plugin as a dev dependency and runs the dry-run in CI, and a contributor who wants to fix a failure locally runs the same command without --dry-run and commits the result.
Where composer-normalize is the wrong tool
The first wrong-tool case is a project that has deliberately arranged its composer.json. The example diff in the README shows blocks moving between sections, and a maintainer who has curated the order of their manifest, or who has comments and section ordering that carry meaning to their team, will find the normalizer undoing it on every run. That is a genuine conflict rather than a misunderstanding, and the tool does not offer a way to keep custom ordering.
The second is anything other than composer.json. The tool normalizes one file, and although it also updates the hash in composer.lock, it does not touch other JSON in a PHP project, does not format PHP source and does not sort other manifests. A team that wants a repository-wide formatting policy needs more than this, and the repository shows the author has those tools separately, with a .php-cs-fixer.php configuration, PHPStan, Rector and a composer-require-checker configuration in the tree.
The third is a project without Composer. A PHP project that does not use Composer has no composer.json to normalize, and the plugin has no other purpose.
MIT, version 2.54.0 and a release every few weeks
The project is licensed MIT, with LICENSE.md at the repository root, and the primary language is PHP. The repository is not archived, and the last push was on 2026-10-18.
The release list shows 2.54.0 on 2026-09-24, 2.53.0 on 2026-09-09 and 2.52.0 on 2026-05-15. Two releases inside September and a gap of four months before that, which is the pattern of a small utility package that is stable and gets a release whenever a dependency or a supported PHP version moves.
The build tooling in the repository explains the packaging. A Makefile pins COMPOSER_VERSION to 2.10.3 and has an it target that runs refactoring, coding-standards, security-analysis, static-code-analysis and tests in sequence. The static analysis side is thorough, with phpstan.neon and a phpstan-baseline.neon, rector.php for automated refactoring, a.php-cs-fixer.php for coding standards, a composer-require-checker.json that verifies the declared dependencies match what the code uses, and a.yamllint.yaml for the workflow files. The CHANGELOG.md at the root is the version history, and the four GitHub Actions workflows visible in the badges, integrate, merge, release and renew, cover the lifecycle from test to publication.
Editorial conclusion
Adopt composer-normalize if you review PHP pull requests and want composer.json diffs to show only what changed, and enforce it in CI with composer normalize --dry-run so an unnormalized manifest fails the build. Do not adopt it if your project has deliberately hand-arranged composer.json, since the normalizer reorders sections and moves blocks, and do not expect it to reorder anything other than composer.json itself. Before you commit, decide the indent style and size once and put them in the composer-normalize section of extra, because that configuration always overrides the command line options, and back up the file on the first run so you can see the full diff before accepting it.
Frequently asked questions
How do I install composer-normalize?
Run composer require --dev ergebnis/composer-normalize and then composer config allow-plugins.ergebnis/composer-normalize true so the plugin is allowed to run. You can also download the composer-normalize.phar from the releases page and make it executable, or install it with phive install ergebnis/composer-normalize.
What does composer normalize do to composer.lock?
The command determines whether a composer.lock exists and whether it is up to date, and it updates the hash in composer.lock when that file exists and an update is necessary. The --no-check-lock option skips the up-to-date check and --no-update-lock stops the lock file from being touched.
How do I configure the indent style for composer-normalize?
Use the --indent-size and --indent-style options, or set them in the composer-normalize section of the extra key in composer.json. The README states that configuration in extra always overrides the command line options, so a project-level setting wins over flags.
How do I make composer-normalize fail a CI build?
Run composer normalize --dry-run in continuous integration. If composer.json is not normalized, or composer.lock is not up to date, the command exits with code 1 and shows a diff, without modifying any files.
What is the current version of composer-normalize?
The release list shows 2.54.0 dated 2026-09-24, after 2.53.0 from 2026-09-09 and 2.52.0 from 2026-05-15. The project is MIT licensed and is published on Packagist.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/ergebnis-composer-normalize)Was this useful?