Laravel Package Development: Providers &amp; Config | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com/public) - [Home](https://www.msaied.com/public)
- [Projects](https://www.msaied.com/public/projects)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [About](https://www.msaied.com/public#about)

           [  Contact](https://www.msaied.com/public#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://www.msaied.com/public)
- [ProjectsCase studies](https://www.msaied.com/public/projects)
- [ArticlesEngineering notes](https://www.msaied.com/public/articles)
- [CertificatesCredentials](https://www.msaied.com/public/certificates)
- [AboutHow I work](https://www.msaied.com/public#about)
- [ContactGet in touch](https://www.msaied.com/public#contact)

  [Start a conversation](https://www.msaied.com/public#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://www.msaied.com/public)
2. /
3. [Articles](https://www.msaied.com/public/articles)
4. /
5. Building a Laravel Package: Service Providers, Auto-Discovery, and Config Merging

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

 Go beyond tutorials and learn how Laravel package internals actually work — from deferred service providers and auto-discovery to safe config merging and testable package bootstrapping.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://www.msaied.com/public#person) Published 29 Jun 2026 · Updated 29 Jun 2026 · 3 min read

ShareCopy linkCopied

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

  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)

 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.

- [laravel](https://www.msaied.com/public/articles?search=laravel)
- [packages](https://www.msaied.com/public/articles?search=packages)
- [service-providers](https://www.msaied.com/public/articles?search=service-providers)
- [architecture](https://www.msaied.com/public/articles?search=architecture)

 Frequently asked questions 
---------------------------

  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.

   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.

   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.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

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

[About](https://www.msaied.com/public#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articleClean Architecture and Pest: Testing Actions, DTOs, and Domain Services Without the Framework](https://www.msaied.com/public/articles/clean-architecture-and-pest-testing-actions-dtos-and-domain-services-without-the-framework) [Next articleLivewire v3 Internals: Morph Markers, JS Hooks, and Alpine Integration](https://www.msaied.com/public/articles/livewire-v3-internals-morph-markers-js-hooks-and-alpine-integration-1)  

   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)

 ###  Have a technical challenge?

 Tell me what you’re building. I reply within two working days.

[Start a conversation](https://www.msaied.com/public#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/740/cce86edc21eddcbdd2f2454fadaf9c70.png)  · 3 min read### The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware

5 Oct 2026 ](https://www.msaied.com/public/articles/the-pipeline-pattern-in-laravel-custom-pipelines-beyond-middleware-1) [ ![](https://cdn.msaied.com/739/2d6897fdcdcf090613f96f72a64b8a78.png)  · 4 min read### MySQL Full-Text Search in Laravel: Indexes, Relevance Scoring, and Boolean Mode

4 Oct 2026 ](https://www.msaied.com/public/articles/mysql-full-text-search-in-laravel-indexes-relevance-scoring-and-boolean-mode) [ ![](https://cdn.msaied.com/738/073696a3fefe18bec825beec5ac658f5.png)  · 4 min read### Laravel Queue Rate-Limited Middleware: Throttling Jobs Without Losing Work

4 Oct 2026 ](https://www.msaied.com/public/articles/laravel-queue-rate-limited-middleware-throttling-jobs-without-losing-work) 

  Have a technical challenge?
----------------------------

Tell me what you’re building. I reply within two working days.

 [Discuss your project ↗](https://www.msaied.com/public#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://www.msaied.com/public)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://www.msaied.com/public/sitemap.xml)
