In one sentence

A service provider is a class that runs while Laravel starts up, so you can register bindings in the container and configure services before any request is handled.

๐ŸŽชPicture a stage crew before a concert. First, every crew member unloads and lists their gear (register). Only once everything is on stage do they plug it all together and run the sound check (boot). Then the doors open.

Why it matters

Where your app gets set up

๐Ÿงฐ

Home for bindings

The container bindings you learned about need a place to live. That place is a provider.

๐Ÿ—๏ธ

Laravel is built on them

Cache, database, mail, queues and sessions are each wired up by a framework provider. Packages ship their own, too.

๐ŸŽ›๏ธ

App-wide settings

Gates, model rules, rate limiters and view composers all get configured in a provider's boot() method.

The big picture

Two passes: register everything, then boot everything

๐Ÿ“œLoad provider listframework + packages + bootstrap/providers.php
๐Ÿ“register()called on every provider: bind things only
๐Ÿš€boot()called on every provider: everything is available
๐Ÿ“จHandle requestrouter, middleware, controller
Laravel finishes all register() calls before it starts any boot() call. That ordering is the whole reason two methods exist.
register()boot()
RunsFirst, on all providersAfter every provider has registered
PurposeTell the container how to build thingsUse services to configure the app
Safe to use other services?โŒ No, they may not be registered yetโœ… Yes
Typical codebind(), singleton(), scoped()Gate::define(), RateLimiter::for(), View::composer(), Model::preventLazyLoading()
Step by step

Working with providers

  1. Meet AppServiceProvider

    A fresh Laravel 11+ app ships with exactly one provider of your own: app/Providers/AppServiceProvider.php. For small and medium apps, it's often the only one you need.

    
              <?php
    
              namespace App\Providers;
    
              use Illuminate\Support\ServiceProvider;
    
              class AppServiceProvider extends ServiceProvider
              {
                  public function register(): void
                  {
                      //
                  }
    
                  public function boot(): void
                  {
                      //
                  }
              }
            
  2. Put bindings in register()

    Inside a provider, $this->app is the container. register() should only describe how to build things. Don't resolve other services here, because their providers may not have run yet.

    
              public function register(): void
              {
                  $this->app->bind(Notifier::class, MailNotifier::class);
                  $this->app->singleton(StripeClient::class, fn () =>
                      new StripeClient(config('services.stripe.secret'))
                  );
              }
            

    For simple class-to-class bindings, providers also offer shortcut properties:

    
              public $bindings = [
                  Notifier::class => MailNotifier::class,
              ];
    
              public $singletons = [
                  PaymentGateway::class => StripeGateway::class,
              ];
            
  3. Configure the app in boot()

    By the time boot() runs, every service is registered, so you can use facades, models and other services freely. You can even type-hint dependencies in boot()'s signature and the container injects them.

    
              use Illuminate\Cache\RateLimiting\Limit;
              use Illuminate\Database\Eloquent\Model;
              use Illuminate\Support\Facades\Gate;
              use Illuminate\Support\Facades\RateLimiter;
    
              public function boot(): void
              {
                  // Catch N+1 queries while developing
                  Model::preventLazyLoading(! app()->isProduction());
    
                  Gate::define('publish-post', fn (User $user) => $user->is_editor);
    
                  RateLimiter::for('comments', fn (Request $request) =>
                      Limit::perMinute(5)->by($request->user()?->id ?: $request->ip())
                  );
              }
            

    You'll recognise these from Gates, Rate Limiting and Eager Loading.

  4. Where providers are listed: bootstrap/providers.php

    In Laravel 11 and later, your app's providers are listed in a tiny file. (Older versions kept a long providers array in config/app.php; you'll still see that in older tutorials.)

    
              <?php
    
              return [
                  App\Providers\AppServiceProvider::class,
              ];
            

    The framework's own providers (cache, database, mailโ€ฆ) are loaded for you, and package providers are found automatically through package discovery: each package declares its provider in its composer.json.

  5. Create your own with make:provider

    When AppServiceProvider gets crowded, split related setup into its own provider, such as one for billing:

    
              php artisan make:provider BillingServiceProvider
            

    This creates app/Providers/BillingServiceProvider.php and adds it to bootstrap/providers.php automatically. If you create a provider file by hand, remember to add it to that list yourself.

    
              class BillingServiceProvider extends ServiceProvider
              {
                  public function register(): void
                  {
                      $this->app->singleton(PaymentGateway::class, StripeGateway::class);
                  }
    
                  public function boot(): void
                  {
                      Gate::define('view-invoices', fn (User $user) => $user->is_admin);
                  }
              }
            
    โ„น๏ธ Deferred providers

    A provider that only registers bindings can implement Illuminate\Contracts\Support\DeferrableProvider and list its services in a provides() method. Laravel then loads it only when one of those services is actually requested.

Common mistakes

Avoid this, do that

โŒ Avoid

          public function register(): void
          {
              Gate::define('publish-post', ...);
              View::share('categories', Category::all());
          }
        

Using services in register() can fail because the services you're calling may not be registered yet.

โœ… Do

          public function boot(): void
          {
              Gate::define('publish-post', ...);
              View::composer('layouts.app', CategoryComposer::class);
          }
        

Bind in register(), use and configure in boot().

โŒ Avoid

          public function boot(): void
          {
              $this->settings = Setting::all(); // runs on EVERY request
          }
        

boot() runs on every request and every Artisan command, even migrate on an empty database.

โœ… Do

          $this->app->singleton(Settings::class, fn () =>
              new Settings(Setting::pluck('value', 'key'))
          );
        

Make heavy work lazy: wrap it in a binding so it runs only when something actually needs it.

๐Ÿ’ก Pro tip: keep providers thin

A provider should wire things together, not contain business logic. If a closure in a provider grows past a few lines, move that logic into a dedicated class (see Service Classes).

๐Ÿ‹๏ธ Try it yourself
  1. Open bootstrap/providers.php in a fresh project and check which providers are listed.
  2. Run php artisan make:provider BlogServiceProvider and confirm it was added to that file.
  3. In its register(), bind a Notifier interface to a LogNotifier class.
  4. In its boot(), call Model::preventLazyLoading(! app()->isProduction()), then trigger an N+1 query and watch the exception appear.
Recap

Key takeaways

Check yourself

Mini quiz