Building a Laravel Package: Service Providers Guide | 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 Package Architecture Matters ](#why-package-architecture-matters)
2. [  Structuring the Package Root ](#structuring-the-package-root)
3. [  The Service Provider in Detail ](#the-service-provider-in-detail)
4. [  register() vs boot() ](#coderegistercode-vs-codebootcode)
5. [  Auto-Discovery ](#auto-discovery)
6. [  Config Merging — The Subtle Trap ](#config-merging-the-subtle-trap)
7. [  Testing the Package in Isolation ](#testing-the-package-in-isolation)
8. [  Takeaways ](#takeaways)

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

  #laravel   #packages   #service-providers   #php  

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

     22 Aug 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   Structuring the Package Root  ](#structuring-the-package-root)
3. [  03   The Service Provider in Detail  ](#the-service-provider-in-detail)
4. [  04   register() vs boot()  ](#coderegistercode-vs-codebootcode)
5. [  05   Auto-Discovery  ](#auto-discovery)
6. [  06   Config Merging — The Subtle Trap  ](#config-merging-the-subtle-trap)
7. [  07   Testing the Package in Isolation  ](#testing-the-package-in-isolation)
8. [  08   Takeaways  ](#takeaways)

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

Dropping reusable code into `app/` is fine for a single project. The moment that code needs to live in three codebases, or you want to open-source it, you need a real package. Laravel's package primitives — service providers, auto-discovery, and the config/view/migration publishing pipeline — are mature and opinionated. Understanding them at the seams saves hours of debugging.

---

Structuring the Package Root
----------------------------

A minimal layout that scales:

```
my-vendor/my-package/
├── src/
│   ├── MyPackageServiceProvider.php
│   ├── MyPackageManager.php
│   └── Console/
│       └── InstallCommand.php
├── config/
│   └── my-package.php
├── database/migrations/
├── resources/views/
├── tests/
├── composer.json
└── README.md

```

Keep `src/` clean. Never put config or views inside `src/` — the publishing pipeline expects them at the package root.

---

The Service Provider in Detail
------------------------------

```php
namespace MyVendor\MyPackage;

use Illuminate\Support\ServiceProvider;

class MyPackageServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // Merge package defaults under the host app's config.
        // Host values WIN — this is the correct merge direction.
        $this->mergeConfigFrom(
            __DIR__.'/../config/my-package.php',
            'my-package'
        );

        $this->app->singleton(MyPackageManager::class, function ($app) {
            return new MyPackageManager(
                $app['config']['my-package']
            );
        });
    }

    public function boot(): void
    {
        $this->loadMigrationsFrom(__DIR__.'/../database/migrations');
        $this->loadViewsFrom(__DIR__.'/../resources/views', 'my-package');

        if ($this->app->runningInConsole()) {
            $this->publishes([
                __DIR__.'/../config/my-package.php' => config_path('my-package.php'),
            ], 'my-package-config');

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

            $this->commands([
                Console\InstallCommand::class,
            ]);
        }
    }
}

```

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

`register()` is for **binding** — nothing else. Calling `config()` or resolving services here is a common mistake; other providers haven't run yet. `boot()` fires after all providers are registered, so it's safe to resolve bindings, load routes, and publish assets.

---

Auto-Discovery
--------------

Add the `extra` block to `composer.json` so Laravel registers your provider without any manual step in `config/app.php`:

```json
{
    "extra": {
        "laravel": {
            "providers": [
                "MyVendor\\MyPackage\\MyPackageServiceProvider"
            ],
            "aliases": {
                "MyPackage": "MyVendor\\MyPackage\\Facades\\MyPackage"
            }
        }
    }
}

```

Laravel reads this during `composer install`/`update` and writes to `bootstrap/providers.php` (Laravel 11+) or `bootstrap/cache/packages.php` (Laravel 10). Users can opt out per-package via their own `composer.json` `extra.laravel.dont-discover` array.

---

Config Merging — The Subtle Trap
--------------------------------

`mergeConfigFrom` does a **shallow** merge. Nested arrays in the host config do not deep-merge with package defaults:

```php
// Package default
'options' => ['timeout' => 30, 'retries' => 3]

// Host config publishes only:
'options' => ['timeout' => 60]

// Result after mergeConfigFrom — retries is GONE
'options' => ['timeout' => 60]

```

For nested config, provide a helper or document that users must publish the full config. Alternatively, implement your own deep merge in `register()`:

```php
$this->app->afterResolving('config', function ($config) {
    $package = require __DIR__.'/../config/my-package.php';
    $host = $config->get('my-package', []);
    $config->set('my-package', array_replace_recursive($package, $host));
});

```

---

Testing the Package in Isolation
--------------------------------

Use `orchestra/testbench` — it boots a minimal Laravel application around your package:

```php
use Orchestra\Testbench\TestCase;

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

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

```

Never test a package by symlinking it into a real app during CI — testbench gives you a reproducible, isolated environment.

---

Takeaways
---------

- Put bindings in `register()`, everything else in `boot()` — this order is not optional.
- `mergeConfigFrom` is shallow; document it or implement `array_replace_recursive` for nested defaults.
- Auto-discovery via `composer.json` `extra.laravel` removes the manual provider registration step for consumers.
- Tag your `publishes()` groups so users can cherry-pick config, migrations, or views independently.
- Use `orchestra/testbench` for all package tests; it's the only reliable way to assert provider wiring in CI.

 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-3&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-3) 

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

  3 questions  

     Q01  When should I use mergeConfigFrom versus requiring users to publish the config?        Use mergeConfigFrom for simple, flat configs where sensible defaults cover most use cases. If your config has nested arrays or users are likely to customise deeply, require them to publish the full config file and document that clearly — shallow merging will silently drop nested keys otherwise. 

      Q02  Does auto-discovery work with Laravel 11's bootstrap/providers.php?        Yes. Laravel 11 moved from the cached packages.php approach to a first-class bootstrap/providers.php file, but the composer.json extra.laravel.providers array is still the correct way to declare your provider. Composer's post-autoload-dump scripts handle writing it into the bootstrap file automatically. 

      Q03  How do I prevent a package's migrations from running automatically in the host app?        Call loadMigrationsFrom in your service provider for convenience, but also publish them with a tag. Users who want full control can remove the auto-load by overriding the provider or simply not calling php artisan migrate until they've reviewed the published files. 

  Continue reading

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

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

 [ ![Laravel Reverb in Production: Scaling WebSockets Beyond a Single Server](https://cdn.msaied.com/580/851fec3976838708af1706f705fe70cd.png) laravel reverb websockets 

### Laravel Reverb in Production: Scaling WebSockets Beyond a Single Server

Running Laravel Reverb on a single node is easy. Scaling it across multiple workers, handling reconnects grace...

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

 22 Aug 2026     4 min read  

  Read    

 ](https://msaied.com/articles/laravel-reverb-in-production-scaling-websockets-beyond-a-single-server-1) [ ![Filament v3 to v4 Migration: Breaking Changes and Practical Refactor Patterns](https://cdn.msaied.com/579/88c4b61835f17b2248e3e39a0e3e765f.png) filament laravel upgrade 

### Filament v3 to v4 Migration: Breaking Changes and Practical Refactor Patterns

Upgrading from Filament v3 to v4 touches forms, tables, actions, and the panel provider API. This guide walks...

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

 22 Aug 2026     4 min read  

  Read    

 ](https://msaied.com/articles/filament-v3-to-v4-migration-breaking-changes-and-practical-refactor-patterns-2) [ ![Filament v4 Render Hooks: Injecting UI Into Any Panel Without Hacking Core](https://cdn.msaied.com/578/2db9d4fbfbbcbb937c0fdb9074a522c6.png) filament laravel filament-v4 

### Filament v4 Render Hooks: Injecting UI Into Any Panel Without Hacking Core

Render hooks let you surgically inject Blade or Livewire content into Filament panels at named slots — no core...

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

 22 Aug 2026     3 min read  

  Read    

 ](https://msaied.com/articles/filament-v4-render-hooks-injecting-ui-into-any-panel-without-hacking-core) 

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