Laravel Package: Providers, Auto-Discovery &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. Laravel Package Development: Service Providers, Auto-Discovery, and Config Merging

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

 Build a production-ready Laravel package from scratch — covering service provider design, auto-discovery via composer.json, config merging, and the subtle traps that break downstream apps.

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

ShareCopy linkCopied

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

  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)

 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.

- [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)
- [composer](https://www.msaied.com/public/articles?search=composer)
- [architecture](https://www.msaied.com/public/articles?search=architecture)

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

  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.

   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.

   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.

   ![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 Testing with Pest: Actions, Fakes, and Boundary Contracts](https://www.msaied.com/public/articles/clean-architecture-testing-with-pest-actions-fakes-and-boundary-contracts) [Next articleLaravel Livewire v3 Internals: Morph Markers, JS Hooks, and Alpine Integration](https://www.msaied.com/public/articles/laravel-livewire-v3-internals-morph-markers-js-hooks-and-alpine-integration)  

   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)

 ###  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)
