Laravel Package Development: Providers &amp; Config | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com) [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles  ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

  [ home ](https://msaied.com)    [ articles ](https://msaied.com/articles)    Building a Laravel Package: Service Providers, Auto-Discovery, and Config Merging        On this page       1. [  Why Most Package Tutorials Stop Too Early ](#why-most-package-tutorials-stop-too-early)
2. [  Auto-Discovery: What Composer Actually Does ](#auto-discovery-what-composer-actually-does)
3. [  Deferred Providers: Load Only When Needed ](#deferred-providers-load-only-when-needed)
4. [  Config Merging Without Clobbering User Values ](#config-merging-without-clobbering-user-values)
5. [  Publishing Assets Cleanly ](#publishing-assets-cleanly)
6. [  Testing the Package in Isolation with Orchestra Testbench ](#testing-the-package-in-isolation-with-orchestra-testbench)
7. [  Key Takeaways ](#key-takeaways)

  ![Building a Laravel Package: Service Providers, Auto-Discovery, and Config Merging](https://cdn.msaied.com/319/a6dd470e38f7779e23bb29b299740978.png)

  #laravel   #packages   #service-providers   #architecture  

 Building a Laravel Package: Service Providers, Auto-Discovery, and Config Merging 
===================================================================================

     29 Jun 2026      3 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   Why Most Package Tutorials Stop Too Early  ](#why-most-package-tutorials-stop-too-early)
2. [  02   Auto-Discovery: What Composer Actually Does  ](#auto-discovery-what-composer-actually-does)
3. [  03   Deferred Providers: Load Only When Needed  ](#deferred-providers-load-only-when-needed)
4. [  04   Config Merging Without Clobbering User Values  ](#config-merging-without-clobbering-user-values)
5. [  05   Publishing Assets Cleanly  ](#publishing-assets-cleanly)
6. [  06   Testing the Package in Isolation with Orchestra Testbench  ](#testing-the-package-in-isolation-with-orchestra-testbench)
7. [  07   Key Takeaways  ](#key-takeaways)

 Why Most Package Tutorials Stop Too Early
-----------------------------------------

Most guides show you how to create a `ServiceProvider`, register a binding, and call it done. Production packages need more: deferred loading, safe config merging that doesn't clobber user values, reliable auto-discovery, and a test harness that bootstraps the package in isolation. Let's work through each layer.

---

Auto-Discovery: What Composer Actually Does
-------------------------------------------

Laravel reads the `extra.laravel` key in your `composer.json` during `composer install` and writes discovered providers/aliases into `bootstrap/cache/packages.php`.

```json
{
  "extra": {
    "laravel": {
      "providers": [
        "Acme\\Auditor\\AuditorServiceProvider"
      ],
      "aliases": {
        "Auditor": "Acme\\Auditor\\Facades\\Auditor"
      }
    }
  }
}

```

If you want users to opt in rather than auto-load, omit the `extra.laravel` block and document the manual registration step. Never assume auto-discovery is always desirable — heavy providers that touch the database or filesystem should be opt-in.

---

Deferred Providers: Load Only When Needed
-----------------------------------------

A provider that registers a single binding doesn't need to boot on every request. Implement `DeferrableProvider` and declare `provides()`:

```php
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;

class AuditorServiceProvider extends ServiceProvider implements DeferrableProvider
{
    public function register(): void
    {
        $this->app->singleton(AuditorManager::class, function ($app) {
            return new AuditorManager($app['config']['auditor']);
        });
    }

    public function provides(): array
    {
        return [AuditorManager::class];
    }
}

```

Laravel caches the `provides()` map. The provider's `register()` is only called the first time `AuditorManager::class` is resolved. This matters in Octane environments where the container is reused across requests.

---

Config Merging Without Clobbering User Values
---------------------------------------------

The naive approach overwrites user config:

```php
// BAD — overwrites published config
$this->app['config']->set('auditor', require __DIR__.'/../config/auditor.php');

```

Use `mergeConfigFrom()` instead. It only fills keys that don't already exist:

```php
public function register(): void
{
    $this->mergeConfigFrom(__DIR__.'/../config/auditor.php', 'auditor');
}

```

**Caveat:** `mergeConfigFrom` is a shallow merge. Nested arrays are replaced wholesale if the user has published and partially customised them. For deep merging, do it explicitly:

```php
public function register(): void
{
    $packageConfig = require __DIR__.'/../config/auditor.php';
    $userConfig    = $this->app['config']->get('auditor', []);

    $this->app['config']->set(
        'auditor',
        array_replace_recursive($packageConfig, $userConfig)
    );
}

```

This ensures user overrides win at every nesting level.

---

Publishing Assets Cleanly
-------------------------

Group publishable assets with tags so users can publish selectively:

```php
public function boot(): void
{
    $this->publishes([
        __DIR__.'/../config/auditor.php' => config_path('auditor.php'),
    ], 'auditor-config');

    $this->publishes([
        __DIR__.'/../database/migrations' => database_path('migrations'),
    ], 'auditor-migrations');

    $this->loadMigrationsFrom(__DIR__.'/../database/migrations');
}

```

Use `loadMigrationsFrom()` so tests and fresh installs run migrations automatically, but still allow users to publish and customise them when needed.

---

Testing the Package in Isolation with Orchestra Testbench
---------------------------------------------------------

Orchestra Testbench gives you a minimal Laravel application without a full project:

```php
use Orchestra\Testbench\TestCase;

class AuditorTest extends TestCase
{
    protected function getPackageProviders($app): array
    {
        return [AuditorServiceProvider::class];
    }

    protected function defineEnvironment($app): void
    {
        $app['config']->set('auditor.driver', 'database');
    }

    public function test_manager_resolves(): void
    {
        $manager = $this->app->make(AuditorManager::class);
        $this->assertInstanceOf(AuditorManager::class, $manager);
    }
}

```

This pattern lets CI validate the full provider lifecycle — registration, booting, config merging — without any host application.

---

Key Takeaways
-------------

- Use `DeferrableProvider` for bindings that aren't needed on every request; declare `provides()` accurately.
- `mergeConfigFrom()` is shallow — use `array_replace_recursive` when your config has nested arrays users might partially override.
- Tag publishable assets so users can selectively publish config, migrations, or views.
- `loadMigrationsFrom()` keeps tests and fresh installs working without requiring a publish step.
- Orchestra Testbench is non-negotiable for package CI; test the full provider lifecycle, not just unit logic.
- Omit `extra.laravel` for heavy providers that should be opt-in rather than auto-discovered.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fmsaied.com%2Farticles%2Fbuilding-a-laravel-package-service-providers-auto-discovery-and-config-merging&text=Building+a+Laravel+Package%3A+Service+Providers%2C+Auto-Discovery%2C+and+Config+Merging) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fmsaied.com%2Farticles%2Fbuilding-a-laravel-package-service-providers-auto-discovery-and-config-merging) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  When should I use a deferred service provider?        Whenever your provider registers bindings that aren't needed on every request — database loggers, report drivers, optional integrations. Deferring them avoids unnecessary instantiation and is especially valuable under Octane where the container persists across requests. 

      Q02  Why does mergeConfigFrom not work for nested config keys?        mergeConfigFrom performs a single-level array_merge. If a user publishes your config and changes a nested key, the entire nested array from the package default replaces their version. Use array_replace_recursive with user config taking priority to handle deep structures safely. 

      Q03  Do I need to publish migrations, or is loadMigrationsFrom enough?        loadMigrationsFrom is sufficient for most packages — it runs migrations automatically in tests and on fresh installs. Offer a publishable tag as well so users who need to customise the schema (adding columns, changing indexes) can do so without forking your package. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://msaied.com/articles) 

 [ ![Laravel 13: New Features, Helpers, and Practical Upgrade Notes](https://cdn.msaied.com/339/58c4fa6fe9b6d25a2dac17c621b6f4c6.png) laravel laravel-13 upgrade 

### Laravel 13: New Features, Helpers, and Practical Upgrade Notes

Laravel 13 ships with async-first defaults, a leaner bootstrapping layer, and several quality-of-life helpers....

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 1 Jul 2026     3 min read  

  Read    

 ](https://msaied.com/articles/laravel-13-new-features-helpers-and-practical-upgrade-notes) [ ![Laravel 12: Structured Route Files, Slim Skeletons, and the New Application Bootstrapping](https://cdn.msaied.com/337/05b39d16d0f88a5fb94d0cf74049b88b.png) laravel laravel-12 upgrade 

### Laravel 12: Structured Route Files, Slim Skeletons, and the New Application Bootstrapping

Laravel 12 ships with a leaner skeleton, first-class route file organisation, and a revised application bootst...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 1 Jul 2026     3 min read  

  Read    

 ](https://msaied.com/articles/laravel-12-structured-route-files-slim-skeletons-and-the-new-application-bootstrapping) [ ![Laravel API Resources: Sparse Fieldsets, Conditional Relationships, and Versioning](https://cdn.msaied.com/336/89d518450335e8fcdaa5be882cf4dd3e.png) laravel api resources 

### Laravel API Resources: Sparse Fieldsets, Conditional Relationships, and Versioning

Go beyond basic API resources. Learn how to implement sparse fieldsets, conditionally load relationships, and...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 1 Jul 2026     3 min read  

  Read    

 ](https://msaied.com/articles/laravel-api-resources-sparse-fieldsets-conditional-relationships-and-versioning) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

Explore

- [Home](https://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [Contact](https://msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
