Copied!
Laravel
PHP

Laravel Service Providers – Complete Guide to register(), boot() & Deferred Providers

Laravel Service Providers – Complete Guide to register(), boot() & Deferred Providers
Shahroz Javed
Apr 10, 2026 . 79 views

What is a Service Provider?

A Service Provider is the central place where your application is wired up. Everything Laravel boots — the mailer, queue, cache, auth system, routes, events — is set up through a service provider.

A service provider is where you:

  • Bind interfaces to implementations (in register())

  • Register event listeners and model observers

  • Register middleware

  • Define routes and route macros

  • Set up view composers, Blade directives, validation rules (in boot())

  • Configure third-party packages

Think of service providers as your application's architecture manifest — reading all of them tells you exactly what your app does and how it's wired.

The Two Phases – register() vs boot()

Every service provider has two phases. Understanding the difference between them is the single most important thing to know about providers.

PHASE 1 — register()
─────────────────────────────────────────────────────────────────
Called first on ALL providers before any boot() runs.
Purpose: bind things into the service container — ONLY.

✅ DO:   $this->app->bind(...)
✅ DO:   $this->app->singleton(...)
❌ DON'T: use other services (they may not be registered yet)
❌ DON'T: register event listeners, routes, view composers here

PHASE 2 — boot()
─────────────────────────────────────────────────────────────────
Called after ALL providers have completed register().
Every service is available and safe to use here.

✅ DO:   View::composer(...)
✅ DO:   Event::listen(...)
✅ DO:   Route::macro(...)
✅ DO:   Validator::extend(...)
✅ DO:   Model::observe(...)
✅ DO:   Type-hint dependencies in boot() signature

Execution Order

Provider A → register()  ┐
Provider B → register()  ├── ALL register() calls run first
Provider C → register()  ┘
         ↓
Provider A → boot()      ┐
Provider B → boot()      ├── ALL boot() calls run after
Provider C → boot()      ┘
⚠️ Common mistake: calling another service inside register(). If that service hasn't been registered yet, you'll get a "not found in container" error. Move cross-service logic to boot().

Anatomy of a Service Provider

<?php

namespace App\Providers;

use App\Services\Riak\Connection;
use Illuminate\Support\ServiceProvider;

class RiakServiceProvider extends ServiceProvider
{
    /**
     * PHASE 1 — bind into container only
     * Called before any boot() method runs.
     */
    public function register(): void
    {
        $this->app->singleton(Connection::class, function ($app) {
            return new Connection(config('riak'));
        });
    }

    /**
     * PHASE 2 — configure app behavior
     * All services are now registered and safe to use.
     */
    public function boot(): void
    {
        // View composers, event listeners, model observers, macros...
    }
}

// Key property available in both methods:
// $this->app  →  the Service Container itself

Shorthand – $bindings & $singletons Properties

For simple interface → implementation mappings with no custom closure logic, declare them as class properties instead of writing a register() method. Laravel reads these automatically.

<?php

class AppServiceProvider extends ServiceProvider
{
    // Registered as bind() calls
    public $bindings = [
        ServerProvider::class => DigitalOceanServerProvider::class,
    ];

    // Registered as singleton() calls
    public $singletons = [
        DowntimeNotifier::class => PingdomDowntimeNotifier::class,
        ServerProvider::class   => ServerToolsProvider::class,
    ];
}

// When to use properties vs register():
//   Properties   → simple class-to-class bindings, no extra setup
//   register()   → need closures, config values, or sub-dependencies

Boot Method Dependency Injection

The boot() method supports full type-hinted dependency injection — exactly like controllers. The container resolves and injects whatever you declare.

public function boot(ResponseFactory $response): void
{
    // Add a custom macro to the response factory
    $response->macro('serialized', function (mixed $value) {
        return response()->json(['data' => serialize($value)]);
    });
}

// This works because by the time boot() runs,
// everything has been registered into the container.

Registering Providers

All providers are listed in bootstrap/providers.php:

<?php

// bootstrap/providers.php
return [
    App\Providers\AppServiceProvider::class,
    App\Providers\ComposerServiceProvider::class,
    App\Providers\PaymentServiceProvider::class,
];

Auto-Registration with Artisan

php artisan make:provider MyServiceProvider
# Creates app/Providers/MyServiceProvider.php
# AND automatically adds it to bootstrap/providers.php

If you create a provider class by hand, add it to providers.php yourself.

Deferred Providers – Performance Optimization

By default, every provider in bootstrap/providers.php is loaded on every request — even if its services are never used. Deferred providers fix this.

A deferred provider tells Laravel: "only load me when someone actually asks for my service." Laravel caches the deferred service map and loads the provider on demand.

<?php

namespace App\Providers;

use App\Services\Riak\Connection;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;

class RiakServiceProvider extends ServiceProvider implements DeferrableProvider
{
    public function register(): void
    {
        $this->app->singleton(Connection::class, function ($app) {
            return new Connection($app['config']['riak']);
        });
    }

    // Tell Laravel exactly what this provider offers
    public function provides(): array
    {
        return [Connection::class];
    }
}

What Happens Internally

App boots
  → Laravel reads provides() from all deferred providers
  → Stores map: "Connection::class → RiakServiceProvider"
  → Does NOT load RiakServiceProvider yet ← performance win

Someone calls app(Connection::class) or type-hints Connection
  → Laravel checks its deferred map
  → Loads RiakServiceProvider NOW (register + boot)
  → Returns the Connection instance

When to Defer

✅ Defer when: provider ONLY registers container bindings
✅ Defer when: service is not needed on every request
              (e.g. Riak, Stripe, specialized integrations)

❌ Don't defer if: boot() registers global event listeners
❌ Don't defer if: boot() has side effects needed on every request
                  (route registration, middleware, global observers)

What Goes in Each Provider

register() — Container Bindings Only

public function register(): void
{
    $this->app->bind(Interface::class, Implementation::class);
    $this->app->singleton(Cache::class, fn($app) => new Cache($app['config']));
    $this->app->instance('key', $prebuiltObject);
}

boot() — Everything Else

public function boot(): void
{
    // View composers
    View::composer('profile', ProfileComposer::class);

    // Event listeners
    Event::listen(UserRegistered::class, SendWelcomeEmail::class);

    // Model observers
    User::observe(UserObserver::class);

    // Custom validation rules
    Validator::extend('uppercase', fn($attr, $val) => strtoupper($val) === $val);

    // Route macros
    Route::macro('apiResource', function (...) { /* ... */ });

    // Blade directives
    Blade::directive('datetime', fn($exp) => "<?php echo $exp->format('Y-m-d'); ?>");

    // Gates and policies
    Gate::define('update-post', fn($user, $post) => $user->id === $post->user_id);

    // Response macros
    Response::macro('api', fn($data) => response()->json(['data' => $data]));
}

Built-in Laravel Service Providers

Every feature of the framework is bootstrapped through a provider:

Provider                        What it bootstraps
──────────────────────────────  ──────────────────────────────────────────
AuthServiceProvider             Gates, policies, auth guards
EventServiceProvider            Event → Listener mappings
RouteServiceProvider            Route files (web.php, api.php), rate limiting
DatabaseServiceProvider         Eloquent, DB connections, query builder
QueueServiceProvider            Queue connections, workers, job dispatch
MailServiceProvider             Mailer, transport drivers (SMTP, SES, etc.)
CacheServiceProvider            Cache drivers and repository
FilesystemServiceProvider       Storage disks, Flysystem adapters
BroadcastServiceProvider        WebSocket broadcasting channels
ValidationServiceProvider       Validator factory and rules

Typical Project Provider Structure

app/Providers/
├── AppServiceProvider.php        ← General bindings, misc boot logic
├── AuthServiceProvider.php       ← Gates and policies
├── EventServiceProvider.php      ← Event → Listener mappings
├── RouteServiceProvider.php      ← Route loading, rate limiters
└── (your custom providers)
    ├── PaymentServiceProvider.php
    ├── NotificationServiceProvider.php
    └── ReportingServiceProvider.php

Pro structure for large apps: create one provider per domain area or feature cluster. Keep AppServiceProvider lean — it shouldn't do everything.

Create a Provider – Full Workflow

Step 1 — Generate it

php artisan make:provider PaymentServiceProvider
# Creates app/Providers/PaymentServiceProvider.php
# Adds it to bootstrap/providers.php automatically

Step 2 — Write register() (bind services)

public function register(): void
{
    $this->app->singleton(PaymentGateway::class, function ($app) {
        return new StripeGateway(config('services.stripe.secret'));
    });
}

Step 3 — Write boot() (configure behavior)

public function boot(): void
{
    Event::listen(PaymentFailed::class, NotifyAdminListener::class);
    User::observe(UserObserver::class);
}

Step 4 — Optionally defer it

// Add if this provider only registers bindings and
// the service isn't needed on every request
class PaymentServiceProvider extends ServiceProvider implements DeferrableProvider
{
    public function provides(): array
    {
        return [PaymentGateway::class];
    }
}

Visual – Full Boot Sequence With Providers

Request arrives
     │
     ▼
bootstrap/app.php → creates Application (Service Container)
     │
     ▼
HTTP Kernel bootstrappers run:
     │
     ├── 1. LoadEnvironmentVariables  → reads .env
     ├── 2. LoadConfiguration         → loads config/ files
     ├── 3. HandleExceptions          → registers error handlers
     │
     ├── 4. RegisterProviders
     │       → reads bootstrap/providers.php
     │       → instantiates each provider
     │       → calls register() on each  ← PHASE 1
     │
     └── 5. BootProviders
             → calls boot() on each      ← PHASE 2
     │
     ▼
Middleware Stack → Router → Controller → Response

Common Mistakes

❌ Using a service inside register():
   public function register(): void {
       // WRONG — EventDispatcher may not be registered yet
       Event::listen(...);
   }

❌ Forgetting to add provider to bootstrap/providers.php:
   Your bindings silently don't exist.
   Use make:provider to avoid this.

❌ Putting everything in AppServiceProvider:
   Fine for small apps. For large apps, split by domain.

❌ Deferring a provider that registers event listeners in boot():
   If deferred, boot() only runs when the service is resolved —
   your listeners won't fire if nothing resolves the service first.

❌ Calling DB or external services inside register():
   Config is available, but the DB connection and other services
   may not be. Move cross-service logic to boot().

Pro Developer Insights

1. Service providers ARE your application's architecture manifest

Reading all your providers tells you exactly what your app does, what interfaces are mapped to what implementations, and what behavior is registered. It's the best overview of any Laravel app.

2. Keep register() dumb and boot() expressive

register() should be a mechanical list of container bindings. boot() is where personality lives: observers, macros, composers, listeners.

3. Defer anything not needed every request

Improves boot time for free. A deferred provider for an infrequently-used integration (Riak, Stripe, S3 processing) costs nothing to add and gives measurable boot savings.

4. Use the $bindings / $singletons shorthand

If your register() is just a list of bind/singleton calls with no closures, replace the whole method with property arrays. Much cleaner, same result.

5. Package developers must use service providers

Packages expose their features to host apps via a provider. Add auto-discovery to skip the manual registration step for users:

// In your package's composer.json:
"extra": {
    "laravel": {
        "providers": ["VendorName\\PackageName\\PackageServiceProvider"]
    }
}
// Laravel will automatically register it — no manual providers.php edit needed.

Conclusion

Service Providers are the backbone of Laravel's bootstrapping process. Every feature — framework or custom — enters the application through a provider.

  • register() binds things into the container — nothing else. Never use other services here.

  • boot() configures app behavior — event listeners, observers, macros, view composers.

  • All providers listed in bootstrap/providers.php. Use make:provider to auto-add.

  • Use the $bindings / $singletons shorthand for simple interface mappings.

  • Defer providers that only register bindings for infrequently-used services.

  • Split large apps into domain-specific providers — keep AppServiceProvider lean.

📑 On This Page