Building a Laravel Package: Service Providers Guide | 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
==================================================================================

 A practical deep-dive into authoring a production-ready Laravel package — covering service provider design, auto-discovery wiring, config merging, and the subtle pitfalls that trip up even experienced engineers.

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

ShareCopy linkCopied

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

  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)

 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.

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

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

  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.

   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.

   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.

   ![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 articleLaravel Reverb in Production: Scaling WebSockets Beyond a Single Server](https://www.msaied.com/public/articles/laravel-reverb-in-production-scaling-websockets-beyond-a-single-server-1) [Next articleLivewire v3 Lazy Components, Islands, and Deferred Loading in Practice](https://www.msaied.com/public/articles/livewire-v3-lazy-components-islands-and-deferred-loading-in-practice-1)  

   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)

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