skip to content

Register & Boot Phases

Service providers wire a Laravel app in two passes: register() binds services, then boot() runs once every provider is registered. Resolving too early in register() is a classic bug.

on this pageshow

explore

questions

5

In a Laravel service provider, what belongs in register() and what belongs in boot(), and why does the order matter?

level: juniorimportance: must knowfreq 75%

answer

  1. two passes over every provider
  2. register: container bindings only
  3. boot: after all providers registered
  4. boot() parameters are injected
  5. $bindings and $singletons properties

basics

~20 s

register() should only bind services into the container; boot() configures the app with them, such as macros, view composers, gates and event listeners. Laravel runs every provider's register() before any boot(), so boot() can rely on every registered service.

solid answer

~40 s

Laravel loads providers in two passes. First it calls `register()` on each provider in list order and then applies that provider's `$bindings` and `$singletons` properties; at that point later providers have not registered, so their services may not be bound. Once every provider is registered, the application calls each provider's `boot()` through the container, so `boot()` can type-hint dependencies and rely on every binding existing. That is why the documentation says `register()` should only bind things into the container, while event listeners, routes, macros, view composers, gates and model-wide settings belong in `boot()`. The skeleton's `App\Providers\AppServiceProvider` ships with both methods empty, ready for that split.

code

php · 20 lines
php
<?php

namespace App\Providers;

use App\Services\NewsletterRenderer;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->singleton(NewsletterRenderer::class);
    }

    public function boot(): void
    {
        Gate::define('publish-issue', fn ($user) => $user->is_editor);
    }
}

go deeper

for a junior

Recall the rule: bindings go in register(), everything that uses services goes in boot(), and every register() runs before any boot().

for a middle

Explain the two passes, when $bindings and $singletons are applied, and why boot() can type-hint dependencies while the constructor cannot.

for a senior

Spot register() code that resolves services or has side effects, and explain the order-dependent failures and duplicate instances it causes.

for a principal

Decide how a large app splits providers by module so each one's register() stays pure and its boot() stays small and discoverable.

## What a service provider is A **service provider** is a class extending `Illuminate\Support\ServiceProvider` that tells Laravel how to set up part of the application. The framework's own features (database, mail, queue, views) each come from providers, packages ship their own, and your app starts with one: `App\Providers\AppServiceProvider`, listed in `bootstrap/providers.php`. New ones come from `php artisan make:provider`. A provider has two main methods, `register()` and `boot()`, and the whole point is **when** each runs. ## The two passes At startup, after environment variables and configuration are loaded, the application works through its provider list like this: 1. **Register pass.** For each provider in order: create it with `new Provider($app)`, call `register()`, then bind every entry of its `$bindings` property and register every entry of its `$singletons` property. 2. **Boot pass**, which begins only after the register pass has finished: - run the application's `booting` callbacks; - for each provider: its own `booting` callbacks, then `boot()` called through the container, then its own `booted` callbacks; - run the application's `booted` callbacks. During the register pass, a provider further down the list has not run yet. During the boot pass, **every** provider has registered, so every binding exists. ## What goes where | Task | Method | |---|---| | `bind()`, `singleton()`, `scoped()`, `instance()`, contextual `when()` rules | `register()` | | Registering another provider conditionally with `$this->app->register()` | `register()` | | `View::composer()`, Blade directives and components | `boot()` | | `Event::listen()`, model observers | `boot()` | | `Gate::define()`, `Gate::policy()` | `boot()` | | Macros on `Response`, `Str`, `Collection` and friends | `boot()` | | `Model::preventLazyLoading()` and other model-wide settings | `boot()` | ## The $bindings and $singletons properties For simple mappings, a provider can declare them instead of writing `register()` code: ```php public $bindings = [ShippingQuote::class => FlatRateQuote::class]; public $singletons = [NewsletterRenderer::class, SubscriberCache::class => RedisSubscriberCache::class]; ``` - They are applied **after** `register()` returns, so an entry here overrides a binding for the same abstract made inside `register()`. - In `$singletons`, an entry without a key registers the class as a singleton of itself; `$bindings` has no such shorthand. ## Boot method injection Providers are instantiated with `new`, so their constructor only receives the application. `boot()` is different: the application invokes it with the container's `call()`, so any type-hinted parameter is resolved: ```php public function boot(ResponseFactory $response): void { $response->macro('csv', fn (string $body) => $response->make($body, 200, ['Content-Type' => 'text/csv'])); } ``` ## Why the rule exists - **Order independence.** Code in `register()` that uses a service can fail or silently get the wrong object if the provider that binds it comes later in the list. - **Cheap registration.** Registration should only record recipes; building objects there wastes time on every request and can freeze an instance before other providers have extended or replaced it. - **Deferral.** A provider that only binds can later be made deferred; one that does real work in `register()` cannot. - **Predictable boot.** Everything that configures behaviour runs in one well-defined phase, after the container is complete. ## Registering providers at run time A provider can register another provider with `$this->app->register(OtherProvider::class)`. This is how development-only tools are kept out of production: `AppServiceProvider::register()` checks the environment and registers the tool's provider only locally. The call registers the provider immediately; if the application has already booted, it also boots it straight away, so a late registration still ends up fully set up. Registering the same class twice returns the existing instance instead of running it again. ## A quick self-check for any provider - Does `register()` contain anything other than bindings, contextual rules or `$this->app->register()`? Move it to `boot()`. - Does `register()` call `make()`, `app()` or a facade? That is resolution, not registration. - Does `boot()` build services that only some requests need? Consider binding them lazily instead. - Could this provider be deferred? Only if it registers bindings and nothing else. - Is the provider listed in `bootstrap/providers.php`, or registered from another provider?

  • In a Laravel provider, what does an entry without a key in the $singletons array do?
    An integer key means the value is used as both abstract and concrete, so `public $singletons = [NewsletterRenderer::class];` registers that class as a singleton of itself. `$bindings` has no such shorthand; each entry must map an abstract to a concrete.
  • In Laravel, why can a service provider's boot() type-hint services while its constructor cannot?
    Providers are created with `new $provider($app)`, so the constructor receives only the application. `boot()` is invoked through the container's `call()`, which resolves each type-hinted parameter, and by then every provider has registered, so the services exist.

register() is every supplier delivering stock to the shop's back room; boot() is opening the doors once all deliveries are in. A shop that starts selling while deliveries are still arriving will find some shelves empty.

saying these in an interview costs you the question

  • At startup each provider's boot() runs right after its own register(), before the next provider registers.
  • Event listeners and routes should be registered in register() so they exist early.
  • The container injects type-hinted services into a service provider's constructor.
  • Services bound by a provider later in the list are available inside register().
  • In Laravel 13, AppServiceProvider must be listed in config/app.php's providers array.
open as a page

In Laravel 13, how does bootstrap/providers.php decide which service providers load, and what does php artisan make:provider change in it?

level: middleimportance: should knowfreq 40%

basics

~20 s

bootstrap/providers.php returns the array of the app's own provider classes; the skeleton lists only AppServiceProvider. php artisan make:provider creates the class and adds it to that file. Framework defaults and auto-discovered package providers load before these.

open as a page

In a Laravel newsletter app, why does resolving ClickTracker inside NewsletterServiceProvider::register() to build a Mailable macro fail, and how is it fixed?

level: middleimportance: should knowfreq 45%

basics

~20 s

Providers register one at a time, so when NewsletterServiceProvider::register() runs, the provider that binds ClickTracker may not have run yet and resolving it throws. Define the macro in boot() and resolve ClickTracker inside the macro closure.

open as a page

In Laravel, how does a DeferrableProvider with provides() postpone loading, and what breaks when provides() leaves out a binding the provider registers?

level: seniorimportance: should knowfreq 30%

basics

~20 s

A provider implementing DeferrableProvider is skipped at startup; Laravel records its provides() list in bootstrap/cache/services.php and registers it only when one of those services is resolved. A binding missing from provides() never triggers loading, so resolving it fails or auto-wires.

open as a page

In Laravel, when do the application's booting() and booted() callbacks and a provider's own booted() callback run, and what are they used for?

level: middleimportance: nice to knowfreq 15%

basics

~10 s

$app->booting() callbacks run before any provider's boot(); a provider's own booting() and booted() callbacks wrap that provider's boot(); $app->booted() callbacks run after every provider has booted, or immediately if added after boot.

open as a page