skip to content

In a composer.json, what does the scripts section do, and when do post-install-cmd and post-autoload-dump fire?

level: juniorimportance: should knowfreq 48%

answer

  1. event name mapped to commands
  2. install with a lock file present
  3. fires on dump-autoload too
  4. root package only
  5. @php, @composer, static callbacks

basics

~20 s

The scripts section maps Composer event names, or custom command names, to shell commands and static PHP callbacks. post-install-cmd runs after install with a lock file present; post-autoload-dump runs after every autoloader dump, including dump-autoload.

solid answer

~40 s

`scripts` in `composer.json` maps an event name, or a name you invent, to one command or an array of them: shell commands, `Vendor\Class::method` static callbacks, `@php`, `@composer`, `@putenv` and `@other-script` references. `post-install-cmd` fires after `composer install` when a lock file exists; without one, install behaves like an update and fires `post-update-cmd` instead. `post-autoload-dump` fires after the autoloader is written, during install and update and also on `composer dump-autoload`, which makes it the hook for work that needs the fresh autoloader. Only the root package's scripts run; scripts declared by dependencies are ignored. A script exiting non-zero fails the Composer command, and `--no-scripts` skips them all.

code

json · 21 lines
json
{
    "scripts": {
        "post-install-cmd": [
            "@php bin/console cache:warmup"
        ],
        "post-autoload-dump": [
            "App\\Composer\\Hooks::generateRouteCache"
        ],
        "test": [
            "@putenv APP_ENV=test",
            "phpunit"
        ],
        "check": [
            "@test",
            "phpstan analyse"
        ]
    },
    "scripts-descriptions": {
        "check": "Run the test suite and static analysis"
    }
}

go deeper

for a junior

Know that scripts map event names or custom names to commands, and that composer test can run a custom script. Recall post-install-cmd and post-autoload-dump.

for a middle

Explain the lock-file rule: install without a lock fires the update events. Know that post-autoload-dump also fires on dump-autoload and that only the root package's scripts run.

for a senior

Treat hooks as part of the build: they must be idempotent, fail loudly, respect --no-dev through COMPOSER_DEV_MODE, and not depend on vendor code in pre-* events.

for a principal

Decide what belongs in Composer hooks versus the deploy pipeline. Warming caches in post-install-cmd couples every install, including CI and developer machines, to production concerns.

## What the scripts section is **Composer scripts** are hooks declared in the root `composer.json` under the `scripts` key. Each key is either a **named event** that Composer fires on its own (such as `post-install-cmd`) or a **custom name** you invent (such as `test`), and each value is one command or an array of commands that run in the order written. A single entry can mix several kinds of command: - a **shell command**, such as `phpunit -c app/` or `rm -rf var/cache/*`; - a **static PHP callback**, written `MyVendor\\MyClass::warmCache`, which receives a `Composer\Script\Event` object and must be autoloadable through the project's `psr-4`, `psr-0` or `classmap` rules; - `@php script.php`, which runs the same PHP binary that is running Composer; - `@composer install`, which calls whichever Composer binary is currently running; - `@putenv NAME=value`, which sets an environment variable in a cross-platform way; - `@other-script`, which calls another entry of the same `scripts` map. Before running shell commands, Composer temporarily puts its `bin-dir` (normally `vendor/bin`) at the front of `PATH`, so `phpunit` resolves to the project's own copy. ## When the two named events fire | Event | Fires when | |---|---| | `pre-install-cmd` / `post-install-cmd` | before / after `composer install` **with a lock file present** | | `pre-update-cmd` / `post-update-cmd` | before / after `composer update`, **or** `install` run without a lock file | | `pre-autoload-dump` / `post-autoload-dump` | before / after the autoloader is written — during install, update **and** `composer dump-autoload` | | `post-create-project-cmd` | after `composer create-project` finishes | Two details catch people out: 1. A fresh checkout with no `composer.lock` runs `composer install` as a resolve-and-lock operation, so `post-update-cmd` fires, not `post-install-cmd`. A hook that must run on every install belongs on both events, or on `post-autoload-dump`. 2. The autoload-dump pair are the only events in this table that also fire on a bare `composer dump-autoload`. Code-generation or cache-clearing steps that need the **new** autoloader therefore usually hang here. A PHP callback on this event can `require` `vendor/autoload.php` itself to use freshly autoloaded functions. The docs also warn against `pre-install-cmd` and `pre-update-cmd` scripts that rely on Composer-managed packages: at that moment the vendor directory may be empty or stale. ## Root-only execution and failure Only scripts in the **root package** run. If a library you require declares its own `post-install-cmd`, Composer ignores it. A dependency that wants to run code during a Composer run has to ship as a **plugin**, which is a separate mechanism gated by the `allow-plugins` config. When a script exits non-zero, Composer prints `Script ... handling the ... event returned with error code N` and the whole command fails with a non-zero status. Packages already written to `vendor/` stay there, so a failed hook in CI must fail the build rather than be ignored. Useful controls: - `--no-scripts` (a global option) skips every script for that run; `COMPOSER_SKIP_SCRIPTS=post-install-cmd` skips only the listed events. - `process-timeout` (default **300** seconds) limits each script; `0` disables it, and `composer run-script --timeout=0 name` does so for a single call. - `COMPOSER_DEV_MODE` is set to `1` or `0` in the script environment, depending on whether the run used `--no-dev`. ## Scripts in container builds A common image build installs dependencies **before** copying the application source, so the `vendor/` layer can be cached. At that moment a root callback such as `App\Composer\Hooks::generateRouteCache` cannot be autoloaded, because `src/` is not in the image yet, so that step runs with `--no-scripts`. The build must then run the skipped work once the source is present: - run the hooks explicitly with `composer run-script post-install-cmd`, or - regenerate the autoloader, which fires `post-autoload-dump` again. Forgetting this second half is a classic bug: the image builds, every test of the build passes, and the caches the hooks were meant to produce are simply missing in production. ## Custom scripts as project commands A key that is not an event name becomes a command: `"test": "phpunit"` is run with `composer test` or `composer run-script test`, and extra arguments after `--` are appended (`composer test -- --filter Invoice`). `scripts-descriptions` supplies the help text shown by `composer list`. Teams use this to give every project the same entry points (`composer test`, `composer lint`, `composer stan`) regardless of which tools sit behind them.

  • Your post-install-cmd hook never runs on a fresh clone in CI, but runs locally. Why?
    The CI checkout probably has no `composer.lock`, or it is ignored. Without a lock file, `composer install` resolves and writes one like an update does, so Composer fires `pre-update-cmd`/`post-update-cmd` instead of the install events. Commit the lock file for an application, or move the hook to `post-autoload-dump`, which fires on both paths.
  • How do you pass arguments to a custom Composer script?
    Append them after `--`: `composer test -- --filter Invoice` adds `--filter Invoice` to the end of the `phpunit` command. A PHP callback reads them with `$event->getArguments()`. Since Composer 2.8 you can put `@additional_args` where the arguments should go, or `@no_additional_args` to drop them for one command.

saying these in an interview costs you the question

  • Scripts declared by required libraries also run during install.
  • post-install-cmd always fires on composer install, lock file or not.
  • A failing script only prints a warning and the install succeeds.
  • post-autoload-dump runs only during composer install.
  • Script callbacks can be instance methods on any class.