Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Composer Normalize Laravel Package

ergebnis/composer-normalize

A Composer plugin that normalizes composer.json, sorting and formatting it consistently so contributors don't argue over style. Install it as a dev dependency, allow the plugin, and run it locally or in CI to keep JSON clean and reduce merge conflicts.

View on GitHub
Deep Wiki
Context7

Getting Started

Install as a dev dependency in the app or package whose composer.json you want to normalize:

composer require --dev ergebnis/composer-normalize

Allow the plugin (required on Composer 2.2+). Without this the normalize command never registers and you'll get a "command not found" style failure:

composer config allow-plugins.ergebnis/composer-normalize true

Commit the resulting config.allow-plugins entry so every teammate and CI runner gets it.

First use case — clean up a fresh or inherited composer.json before your next PR. Preview first, then apply:

composer normalize --diff      # show what would change, don't write
composer normalize             # write normalized composer.json

The command will:

  1. Locate composer.json (or the path you pass as the file argument).
  2. Verify composer.lock exists and is in sync (unless --no-check-lock).
  3. Normalize via Ergebnis\Json\Normalizer\Vendor\Composer\ComposerJsonNormalizer.
  4. Re-format using the sniffed indentation (or your --indent-size/--indent-style).
  5. Write the file back and update the content-hash in composer.lock if needed.

Where to look first:

  • README "Usage → Details / Arguments / Options" for the exact flag semantics.
  • The examples section of the README — three real-world diffs (pestphp/pest, phpspec/phpspec, phpunit/phpunit) show precisely what the normalizer reorders and collapses, so you know what a big first-run diff will look like.
  • NormalizeCommand / NormalizePlugin in src/ when you need to understand exit codes or lock handling.
  • ergebnis/json-normalizer for the underlying rule engine if you outgrow the defaults.

Implementation Patterns

Preview / write / verify triad

Use three distinct invocations for three distinct jobs:

composer normalize --diff        # local review before committing
composer normalize               # apply changes
composer normalize --dry-run     # CI gate: exit 1 + diff if not normalized

--dry-run is the CI contract: it never writes and exits non-zero when the file is not normalized or the lock is stale.

Pin formatting in extra for deterministic output

Sniffed indentation is fine for a solo repo, but for a team set it explicitly. Note this lives in composer.json itself, so it travels with the repo:

{
    "extra": {
        "composer-normalize": {
            "indent-size": 4,
            "indent-style": "space"
        }
    }
}

Laravel projects conventionally use 4 spaces; match your editor's .editorconfig so composer normalize and IDE format-on-save don't fight.

Wire it into Composer scripts

{
    "scripts": {
        "normalize": "composer normalize",
        "test:normalize": "composer normalize --dry-run",
        "test": [
            "@test:normalize",
            "@test:types",
            "@test:unit"
        ]
    }
}

Then composer test catches unnormalized composer.json in the same run as PHPUnit/Pest/PHPStan. Because the script exits 1, the aggregate script fails correctly.

CI: run it after install, before tests

- uses: shivammathur/setup-php@v2
  with:
    php-version: '8.3'
    coverage: none
- run: composer install --no-interaction --no-progress
- run: composer normalize --dry-run

composer install triggers plugin registration; the dry run then guarantees contributors can't merge a hand-reordered composer.json. If your CI installs with --no-plugins for speed or security, the normalize command simply won't exist — call the phar instead (see below).

Git hooks / task runners

  • CaptainHook: register composer normalize --dry-run as a pre-commit action.
  • GrumPHP: shell task wrapping composer normalize --dry-run.
  • Husky/lint-staged style: run composer normalize on staged composer.json only.

For hooks that must not modify files mid-commit, always use --dry-run.

Laravel package development

When you maintain a package, normalization gives you a stable, reviewable manifest across releases. Practical flow before tagging:

composer validate --strict
composer normalize
composer normalize --dry-run   # confirm idempotence

The second dry run should be a no-op — that also proves you didn't leave the lock hash stale.

Monorepos and multiple manifests

Pass the path explicitly for each nested manifest:

composer normalize packages/billing/composer.json
composer normalize packages/auth/composer.json

There's no recursive mode; loop in a shell script or Makefile target. Keep each package's extra.composer-normalize block consistent.

Non-Composer environments (CI images, legacy jobs)

Use the distributed phar or Phive when you can't or won't allow plugins:

curl -L -o composer-normalize.phar https://github.com/ergebnis/composer-normalize/releases/latest/download/composer-normalize.phar
chmod +x composer-normalize.phar
./composer-normalize.phar --dry-run

Pin the release tag in CI rather than latest so a normalizer upgrade doesn't turn your pipeline red overnight.

Isolating lock failures from format failures

If your CI's lock file can legitimately be out of date (e.g., generated in a prior stage), split the checks:

composer normalize --dry-run --no-check-lock   # formatting only
composer install --dry-run                     # lock validity separately

Conversely, in a deploy script where you don't want the normalizer touching the lock, use --no-update-lock.

Gotchas and Tips

extra beats the command line. The README is explicit: "configuration provided in composer extra always overrides the configuration provided via command line options." If indent-size: 2 sits in extra, composer normalize --indent-size 4 is silently ignored. When someone reports "the flag does nothing", check extra first.

Lock hash churn. Because normalization rewrites composer.json, the content-hash in composer.lock changes. The command updates it for you by default, which means every normalize produces a two-file commit. That's correct — but it also means --dry-run fails when the lock is stale, which is often not what you're testing. Use --no-check-lock to decouple.

Do the first normalization as an isolated commit. On an unsorted legacy composer.json, the first run reorders require, require-dev, config, scripts, and extra wholesale. Mixing that diff into a feature PR makes review miserable and guarantees merge conflicts. Land it alone, and add the SHA to .git-blame-ignore-revs.

Sorting is opinionated and can collide with Composer's own sorter. config.sort-packages: true affects how Composer writes require during composer require, while the normalizer applies its own ordering (including platform packages like php and ext-*). The results are consistent but not identical to sort-packages alone; expect a diff the first time both are active and don't try to disable one to "fix" it.

Plugin registration failures are quiet-ish. Two common causes: (1) allow-plugins not set, and (2) the whole composer run used --no-plugins (common in Docker builds and hardened CI). In both cases composer normalize is unavailable. Verify with composer normalize --help; if that errors, it's registration, not formatting.

Sniffed indentation perpetuates mistakes. With no extra config, a file that starts with tabs stays tabs. If your repo is inconsistent, set indent-size/indent-style explicitly before the big normalization commit.

It only reformats; it doesn't validate. composer normalize won't tell you that your version constraints are nonsense. Pair it with composer validate --strict in the same CI step — they catch orthogonal problems.

Malformed JSON fails hard. Trailing commas or comments in composer.json (some editors add them) abort the run. That's a feature, but the error message points at JSON parsing, not at your editor.

--diff and --dry-run differ on purpose. --diff shows changes and writes nothing by default — but it's --dry-run that carries the exit-1-on-mismatch contract. Use --dry-run in gates; use --diff for human review.

Exit codes matter for script chaining. --dry-run returning 1 inside a Composer @script array correctly halts the aggregate script. Don't wrap it in || true in CI "just to keep the build green" — that removes the entire value.

Extension points are limited by design. The normalizer's rules live in ergebnis/json-normalizer; the plugin merely wires ComposerJsonNormalizer into a Composer command. There's no config surface for custom sort orders. If you need domain-specific reordering (say, grouping internal packages first), you'd write your own Composer plugin around Ergebnis\Json\Normalizer, or post-process separately. For non-composer.json JSON files, use json-normalizer directly rather than expecting this plugin to handle them.

Team drift. The single highest-leverage habit: add composer normalize --dry-run to CI and to the pre-commit hook. Without both, developers reformat composer.json in their editor and you get a normalization commit every other week.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
taras-koval/laravel-starter
ssofy/laravel
datalogix/laravel-error-pages
pushinbr/pam-octane
9in/laravel-server
9in/laravel-events
9in/laravel
pushinbr/pam-laravel
derian-cordoba/laravel-ab-testing
boring-o11y/unique-job-middleware
boring-o11y/httptheus
ympact/laravel-icon-registry
mattapril/laravel-responsable-reports
edlugz/tanda
daikazu/social-links
hwkdo/intranet-app-packtrack
coloredcow/laravel-audit
ironflow/halo-ui
norialabs/ledger
yangusik/thrun-laravel