ServiceProvider and explains how to maintain config as part of your package’s public API. It assumes you are familiar with Laravel package development, and the implementation was checked against laravel/framework v13.34.0.
Publishing and merging are separate operations
publishes() registers a source and a destination for copying. The user’s config directory does not change until vendor:publish copies the file. In contrast, mergeConfigFrom() updates the config repository at boot time and never modifies the file itself.
register().
mergeConfigFrom() merges only the top level
ServiceProvider::mergeConfigFrom() calls array_merge() with the package config first and the application’s existing config second. For the same string key, the application’s value wins.
The following example reproduces the framework’s merge using plain arrays.
enabled is filled in, but the entire transport array is replaced. transport.retries does not survive. The key point: if a user has already published an older transport array, any new keys you add to that array will not be filled in.
Fill in nested config with replaceConfigRecursivelyFrom()
Laravel 13’sServiceProvider also has a protected method, replaceConfigRecursivelyFrom(). It calls array_replace_recursive() with the same argument order.
If you want a config API where users can override nested string keys individually, change your provider’s register() as follows. You do not need to combine it with the earlier mergeConfigFrom() call for the same config key.
$defaults and $overrides as before produces the following result.
replaceConfigRecursivelyFrom() is a method that exists in the Laravel 13 source code examined on this page. Keep it distinct from mergeConfigFrom(), which the official package development documentation covers, and check the corresponding framework implementation before using it.Numerically keyed lists are not replaced as a whole
Recursive replacement does not mean “swap the whole array for the user’s value.” Even with numeric keys, it replaces values at matching keys and keeps any keys the user did not specify.['slack'], database remains. Passing ['channels' => []] does not empty the default list either. Be especially careful with settings where specifying the whole list matters, such as notification channels or middleware.
If your config has settings like this, start by reconsidering its structure. For example, split lists and partially overridable associative arrays into separate top-level keys and use a shallow merge. Changing the merge strategy in a package you have already released changes behavior for the same config file, so do not treat it as a simple implementation swap.
The config cache stores merged values
Both methods skip merging when the application implementsCachesConfiguration and configurationIsCached() returns true. In a typical Laravel application, this is the case whenever the app boots with a config cache present.
ConfigCacheCommand deletes the old config cache, boots a fresh application, and retrieves the entire config repository. Provider configs are merged during that boot, and the result is saved to the cache file. On subsequent boots, LoadConfiguration loads those values.
As a result, when you update a package and its default values or merge strategy change, applications that keep using an old cache will not see the change. In deployments that use the config cache, rebuild it with the updated code.
php artisan config:clear. Do not choose your own cache location; let Laravel’s commands manage it.
Checks before releasing config changes
In your package tests, treat configs left over from older versions as input, not just unpublished configs.- Required default values are available even when the config is not published.
- With an older published config, the user’s values take precedence and new options are filled in as designed.
- The override contract does not change for nested arrays, numerically keyed lists, and empty arrays.
config:cachesucceeds in a consuming application, and a separate boot using the cache produces the same config.
Related pages
Package testing
Register service providers and verify how your config and services behave.
Version compatibility management
Connect public API changes to your release policy and ongoing maintenance.