Skip to main content
If your package pre-generates its own metadata, simply asking users to add a dedicated command to their deployment steps makes it easy for that step to be missed during updates. With ServiceProvider::optimizes(), you can hook your generation and clearing commands into Laravel’s optimize and optimize:clear. This page assumes you are familiar with the package development basics and examines the implementation in Laravel Framework v13.35.0. It covers the contract for registration and operation, not the format of cache files.

Separate command registration from task registration

commands() registers command classes that can be called from Artisan. optimizes() is a separate step that registers the names of already executable commands as optimization tasks. Calling only the latter does not register the command classes. The following example assumes your package already implements CacheMetadataCommand and ClearMetadataCommand, with $signature values of courier:cache and courier:clear-cache respectively.
All arguments to optimizes() are nullable, so you can register only generation or only clearing. However, always tell your users which step invalidates the cache you generate.

The registration key is also a user-facing contract

ServiceProvider stores generation commands in the static array $optimizeCommands and clearing commands in $optimizeClearCommands. In both cases, key becomes the array key. If you omit key, a name is derived from the provider’s class name. For example, CourierServiceProvider becomes courier. Because only the class name is used, providers with the same name in different namespaces can collide. If you register again under the same key, the command for that side is overwritten by the later value. To register multiple tasks, specify a different key for each. Also avoid the keys of Laravel’s built-in tasks, such as config and routes, because identical string keys are overwritten when the built-in tasks and package tasks are combined.
Explicitly specify a key that identifies your package, such as acme-courier, and keep it stable across releases. The key becomes the task’s display name and is also the value users pass to --except.

Package tasks run after the built-in tasks

In the implementation examined here, both commands spread the package registration array into the built-in task array and then call each task in order. As long as you use a non-colliding key, package tasks are appended after the built-in tasks. Given this order, your generation command should only generate data your package owns, rather than rebuilding other built-in caches. Don’t use optimizes() as an API for controlling dependency order between multiple packages. If you need a strict order, list the dedicated commands explicitly.
optimize:clear also includes cache:clear, which deletes data in the default cache store. If you only want to clear your package’s cache, run courier:clear-cache directly. Design your package’s clearing command to delete only the keys or files it owns, without flushing the entire shared store.

Exclude tasks by key or command name

The --except option of both commands accepts comma-separated values. Leading and trailing whitespace is trimmed from each value, and any task whose key or command name matches is excluded.
The first two exclude the same generation task. The third excludes the package’s clearing task and the built-in cache:clear. cache is a task key, not a package-specific name. Exclusions apply only to that run. They are not a setting that disables the provider’s registration or automatically deletes package caches created earlier.

Distinguish task FAIL from the parent command’s exit code

OptimizeCommand and OptimizeClearCommand call each task with callSilently() and pass whether the exit code is 0 to the task display. Because normal child command output is not shown, run the dedicated command directly to investigate the cause. In Laravel v13.35.0, neither handle() method returns a child command’s non-zero exit code as the parent’s return value. Even if FAIL appears on screen, the loop continues, and if no exception is thrown, the parent command exits with 0. Thrown exceptions, on the other hand, are rethrown by the task display component, so they don’t follow the same continue-on-failure behavior.
Don’t treat an exit code of 0 from php artisan optimize alone as proof that your package cache was generated successfully. This behavior is based on the implementation of the version examined here, so check it again whenever you update your supported Laravel versions.
If package generation is a mandatory deployment requirement, use a procedure that lets you check the child command’s exit code directly. For example, the following excludes the package task from the batch run and runs it directly once after the built-in tasks.
This example reflects a courier:cache failure in the exit code, but it does not aggregate non-zero exits from built-in tasks. For deployments that must strictly detect built-in task failures too, run the required commands individually and check their exit codes.

Design and verify caches that survive updates

Beyond registration, decide the responsibilities of your commands and the code that reads the cache.
  • Generation produces the same state from the same input no matter how many times it runs, and never activates incomplete data after a partial failure.
  • Clearing completes successfully even when no cache exists, and never deletes the user’s published config or persistent data.
  • A command that fails to generate reports an error and returns non-zero. The reading side also never treats a corrupted cache as valid unconditionally.
  • When you change the cache format, tell users they need to regenerate it, and consider restarting long-running processes.
In addition to your package tests, verify the following in a consuming application. Because the registration arrays are static, also watch for registration state carrying over between tests in the same process.

Package config merging and caching

Review how filling in published config relates to rebuilding the config cache.

Testing Laravel packages with Orchestra Testbench

Register providers and Artisan commands in your test environment.

Primary sources

Last modified on October 6, 2026