Laravel Package: Providers, Auto-Discovery &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)    Laravel Package Development: Service Providers, Auto-Discovery, and Config Merging        On this page       1. [  Why Package Architecture Matters ](#why-package-architecture-matters)
2. [  Service Provider Anatomy ](#service-provider-anatomy)
3. [  register() vs boot() ](#coderegistercode-vs-codebootcode)
4. [  Auto-Discovery via composer.json ](#auto-discovery-via-codecomposerjsoncode)
5. [  Config Merging Done Right ](#config-merging-done-right)
6. [  Deferred Providers for Heavy Bindings ](#deferred-providers-for-heavy-bindings)
7. [  Testing Your Package in Isolation ](#testing-your-package-in-isolation)
8. [  Key Takeaways ](#key-takeaways)

  ![Laravel Package Development: Service Providers, Auto-Discovery, and Config Merging](https://cdn.msaied.com/258/673a80fa8e42ae375a4bba21bdcd92ea.png)

  #laravel   #packages   #service-providers   #composer   #architecture  

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

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

       Table of contents

1. [  01   Why Package Architecture Matters  ](#why-package-architecture-matters)
2. [  02   Service Provider Anatomy  ](#service-provider-anatomy)
3. [  03   register() vs boot()  ](#coderegistercode-vs-codebootcode)
4. [  04   Auto-Discovery via composer.json  ](#auto-discovery-via-codecomposerjsoncode)
5. [  05   Config Merging Done Right  ](#config-merging-done-right)
6. [  06   Deferred Providers for Heavy Bindings  ](#deferred-providers-for-heavy-bindings)
7. [  07   Testing Your Package in Isolation  ](#testing-your-package-in-isolation)
8. [  08   Key Takeaways  ](#key-takeaways)

 Why Package Architecture Matters
--------------------------------

Dropping reusable code into a `packages/` directory is easy. Shipping something that installs cleanly, respects host-app configuration, and doesn't pollute the container is hard. This article walks through the decisions that separate a throwaway internal package from one you'd confidently open-source.

---

Service Provider Anatomy
------------------------

Every package starts with a service provider. Keep it thin — registration logic only, no business logic.

```php
namespace Acme\Auditor;

use Illuminate\Support\ServiceProvider;

class AuditorServiceProvider extends ServiceProvider
{
    // Defer binding until the service is actually resolved
    public bool $defer = false;

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

        $this->app->singleton(AuditLogger::class, function ($app) {
            return new AuditLogger(
                $app['config']->get('auditor'),
                $app['db']
            );
        });
    }

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

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

        $this->loadRoutesFrom(__DIR__ . '/../routes/auditor.php');
    }
}

```

### `register()` vs `boot()`

- **`register()`** — bind things into the container. No facades, no other services. Other providers may not be loaded yet.
- **`boot()`** — everything else: routes, views, migrations, event listeners. All providers have been registered by this point.

Violating this order is the single most common cause of "service not found" errors in packages.

---

Auto-Discovery via `composer.json`
----------------------------------

Laravel reads the `extra.laravel` key to register providers and aliases automatically — no manual `config/app.php` edits needed.

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

```

Host apps can opt out per-package in their own `composer.json`:

```json
{
  "extra": {
    "laravel": {
      "dont-discover": ["acme/auditor"]
    }
  }
}

```

This is important for packages that should be explicitly configured before loading.

---

Config Merging Done Right
-------------------------

`mergeConfigFrom` performs a **shallow** merge. Nested arrays in the host app's published config will be completely replaced by the package default if the key exists at the top level. This surprises most developers.

```php
// Package default
[
  'driver' => 'database',
  'channels' => ['slack', 'log'],
]

// Host app publishes and sets only:
[
  'driver' => 'redis',
]

// Result after mergeConfigFrom — 'channels' is MISSING
// because the host key 'auditor' exists, so no merge happens

```

For deep merges, do it manually in `register()`:

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

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

```

---

Deferred Providers for Heavy Bindings
-------------------------------------

If your package registers a service that isn't needed on every request, defer it:

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

class AuditorServiceProvider extends ServiceProvider implements DeferrableProvider
{
    public function provides(): array
    {
        return [AuditLogger::class];
    }

    public function register(): void
    {
        $this->app->singleton(AuditLogger::class, ...);
    }
}

```

Laravel caches the `provides()` list and only boots this provider when `AuditLogger` is actually resolved. Don't defer providers that register routes or listeners — those must run on every request.

---

Testing Your Package in Isolation
---------------------------------

Use `orchestra/testbench` to bootstrap a minimal Laravel app inside your test suite:

```php
use Orchestra\Testbench\TestCase;

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

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

    public function test_logger_resolves_from_container(): void
    {
        $logger = $this->app->make(AuditLogger::class);
        $this->assertInstanceOf(AuditLogger::class, $logger);
    }
}

```

---

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

- Keep `register()` for container bindings only; use `boot()` for everything that touches other services.
- `mergeConfigFrom` is shallow — implement `array_replace_recursive` for nested config safety.
- Auto-discovery via `extra.laravel` removes friction but always document the opt-out path.
- Implement `DeferrableProvider` for heavy services not needed on every request.
- Use `orchestra/testbench` to test your provider lifecycle without a full app install.

 Found this useful?

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

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

  3 questions  

     Q01  Why does my published config override get ignored after mergeConfigFrom?        mergeConfigFrom only fills in missing top-level keys. If the host app has already set the top-level key (even partially), the package defaults for nested keys are dropped. Use array_replace_recursive in register() for a true deep merge. 

      Q02  Should I always use auto-discovery for my package?        Auto-discovery is convenient for general-purpose packages. If your package requires explicit configuration before the provider boots — such as credentials or a driver choice — consider documenting manual registration so developers aren't surprised by a misconfigured singleton on first install. 

      Q03  When should a provider be deferred?        Defer a provider when it only registers container bindings that aren't needed on every request — background services, report generators, or third-party API clients. Never defer providers that register routes, middleware, or event listeners, as those must be available from the first request. 

  Continue reading

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

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

 [ ![Laravel AI SDK: Tool-Calling Agents and Conversation Persistence](https://cdn.msaied.com/260/8c84f424e42da01993c9ba4b8eb19655.png) laravel ai agents 

### Laravel AI SDK: Tool-Calling Agents and Conversation Persistence

Build reliable tool-calling AI agents in Laravel using the Prism package. Learn how to wire tools, persist con...

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

 21 Jun 2026     3 min read  

  Read    

 ](https://msaied.com/articles/laravel-ai-sdk-tool-calling-agents-and-conversation-persistence) [ ![Laravel Livewire v3 Internals: Morph Markers, JS Hooks, and Alpine Integration](https://cdn.msaied.com/259/e8ce445f021c2b26ebe4dd5da50014f8.png) livewire laravel alpine 

### Laravel Livewire v3 Internals: Morph Markers, JS Hooks, and Alpine Integration

Go beyond the docs: understand how Livewire v3 diffs the DOM with morph markers, intercept the lifecycle with...

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

 21 Jun 2026     3 min read  

  Read    

 ](https://msaied.com/articles/laravel-livewire-v3-internals-morph-markers-js-hooks-and-alpine-integration) [ ![Clean Architecture Testing with Pest: Actions, Fakes, and Boundary Contracts](https://cdn.msaied.com/257/eb96b08443e07a2edd8694c0f6f8b524.png) laravel pest clean-architecture 

### Clean Architecture Testing with Pest: Actions, Fakes, and Boundary Contracts

Learn how to test Laravel actions, DTOs, and domain boundaries with Pest using fakes, contract assertions, and...

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

 21 Jun 2026     3 min read  

  Read    

 ](https://msaied.com/articles/clean-architecture-testing-with-pest-actions-fakes-and-boundary-contracts) 

   [  ![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)
