
August 12, 2026
10 min read
By Kokil Thapa | Last reviewed: September 2026
Every Laravel application depends on service providers in Laravel to connect interfaces to implementations before a route runs. They sit between bootstrap/app.php and your controllers. If you place database calls or route logic in the wrong phase, the app works locally and fails after config:cache. This guide covers the lifecycle, binding patterns, and production debugging I use on real Laravel development in Nepal projects and client apps worldwide.
register() binds services into the container without side effects, and boot() runs after all providers register to attach routes, events, views, and middleware safely.The split between registration and booting is where most provider bugs start. I have traced silent production failures to a single env() call inside register(). The examples below target Laravel 13.x on PHP 8.3 or higher, with notes for teams still on Laravel 12. For container fundamentals beyond providers, see the Laravel service container deep dive.
How do service providers in Laravel fit into the request lifecycle?
Laravel does not load every class on every request. It builds the application, registers providers, boots them, then hands control to the HTTP kernel. That order is fixed. You cannot resolve a boot-only dependency during registration.
Provider lists live in bootstrap/providers.php on Laravel 11 and later. Framework and package providers merge into bootstrap/cache/services.php after optimization. That compiled file is the first place I look when a provider works in local but not on a cached VPS.
During registration, assume no other provider has finished loading. During boot, the full container map exists. This isolation prevents circular dependency crashes during startup. Official reference: Laravel service provider documentation.
What is the difference between register and boot methods?
This distinction drives every solid modern Laravel architecture best practice. Registration declares what the container can build. Booting connects those services to framework features.
Register: container bindings only
The register() method should contain bindings and config merges. It receives no arguments. Do not resolve other services here. Do not register event listeners. Think of it as a manifest, not a startup script.
<?php
namespace App\Providers;
use App\Services\Payment\EsewaGateway;
use App\Services\Payment\PaymentGatewayInterface;
use Illuminate\Support\ServiceProvider;
class PaymentServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(PaymentGatewayInterface::class, function ($app) {
return new EsewaGateway(
config('services.esewa.merchant_id'),
config('services.esewa.secret_key')
);
});
// WRONG: resolving another service during register
// $logger = $this->app->make(LoggerInterface::class);
}
} Boot: integration and side effects
After every provider registers, Laravel calls boot() on each one. Here you may type-hint dependencies. Laravel injects them automatically. Routes, view composers, and event listeners belong here.
use Illuminate\Support\Facades\Event;
use App\Events\OrderCreated;
public function boot(PaymentGatewayInterface $gateway): void
{
if ($gateway->isConfigured()) {
$this->loadRoutesFrom(__DIR__.'/../routes/payment-webhooks.php');
Event::listen(OrderCreated::class, function ($event) use ($gateway) {
$gateway->capture($event->order);
});
}
$this->publishes([
__DIR__.'/../config/payment.php' => config_path('payment.php'),
], 'payment-config');
} My team rule is simple. If register() contains anything beyond bind, singleton, or mergeConfigFrom, move it to boot(). That one habit removes an entire class of deploy-time errors tied to PHP opcache configuration for production.
When should you defer a Laravel service provider?
Deferred providers skip registration and boot until the container resolves one of their listed bindings. That saves memory and CPU on routes that never touch heavy services. The win matters on shared hosting and small VPS plans common in Nepal.
Set protected $defer = true and implement provides() with every binding key your provider registers. Laravel maps those keys to the provider class in the compiled services cache.
class PdfGenerationServiceProvider extends ServiceProvider
{
protected $defer = true;
public function register(): void
{
$this->app->singleton(PdfGenerator::class, function () {
return new PdfGenerator(config('pdf.engine'));
});
}
public function provides(): array
{
return [PdfGenerator::class];
}
} Defer PDF engines, Excel exporters, Stripe clients, and AWS SDK wrappers. Keep auth, session, database, and routing providers eager. On a legal-tech portal I maintain, deferring document generators trimmed boot overhead on listing pages that never render PDFs. Similar patterns appear in our Mijar Law Associates client portal work.
How do you bind interfaces and facades in service providers?
Providers are the wiring layer between contracts and concrete classes. Correct bindings power constructor injection, method injection, and facades. Wrong keys produce "Target class does not exist" errors at runtime.
| Binding type | Method | Use case | Lifecycle |
|---|---|---|---|
| Transient | bind() | Stateless utilities needing a fresh instance | New object each resolution |
| Singleton | singleton() | Shared services like gateways or repositories | Built once, cached in container |
| Instance | instance() | Pre-built objects, often test doubles | Always returns same object |
| Contextual | when()->needs()->give() | Different impl per consuming class | Conditional resolution |
For facades, the string returned by getFacadeAccessor() must match the container binding key. Packages often bind an interface but expose a facade under a different alias. That mismatch breaks resolution.
// In register()
$this->app->singleton('payment.gateway', function ($app) {
return new EsewaGateway(...);
});
$this->app->singleton(PaymentGatewayInterface::class, function ($app) {
return $app->make('payment.gateway');
});
// Facade
class Payment extends Facade
{
protected static function getFacadeAccessor(): string
{
return 'payment.gateway';
}
} Bind both the string alias and the interface for packages you ship internally. Consumers can choose facades or constructor injection without forked setup. The Laravel container documentation covers contextual binding and tagged collections in more depth.
For package authors, dual binding pairs naturally with the guide on creating custom Laravel packages and the walkthrough for Laravel package development from scratch.
How do you create and register a custom service provider?
Artisan scaffolds providers quickly. Run php artisan make:provider PaymentServiceProvider. Laravel 11+ may skip auto-registration, so add the class to bootstrap/providers.php manually when needed.
- Generate the provider with Artisan or hand-write a class extending
Illuminate\Support\ServiceProvider. - Place bindings in
register(). Merge package config withmergeConfigFrom()when shipping libraries. - Wire routes, events, policies, and view composers in
boot(). - Register the provider in
bootstrap/providers.phpor rely on package auto-discovery viacomposer.json. - Run
php artisan config:clearlocally, then test withphp artisan config:cachebefore deploy.
Package discovery reads the extra.laravel.providers array in composer.json. After adding a provider, run composer dump-autoload -o on the server. Pair provider tests with guidance from Laravel testing with Pest so register and boot phases both get coverage.
Event wiring belongs in providers, but keep listener classes thin. See Laravel events and listeners real use cases for patterns that avoid boot-time bloat. For API modules, providers often load route files separately from building RESTful APIs with Laravel.
How do you debug service provider issues in production?
Production differs from local because of compiled caches. Commands like config:cache, route:cache, and event:cache bake provider metadata into files under bootstrap/cache/. Bugs that appear only after caching usually come from runtime assumptions during registration.
- Inspect compiled services: Open
bootstrap/cache/services.php. Confirm your provider appears under eager, deferred, or the main providers list. - Ban env() in providers: Use
config()only. Values fromenv()become null afterconfig:cache. - Trace resolution: In Tinker, run
app()->bound(PaymentGatewayInterface::class)andapp()->resolved(PaymentGatewayInterface::class). - Profile boot cost: Use Laravel Debugbar custom panels locally to spot expensive
boot()work. - Validate deploy steps: Follow a zero-downtime Deployer workflow that clears and rebuilds caches after symlink swap.
Never register services based on session data or the current request inside a provider. Move that logic to middleware or a factory resolved lazily. Validate JSON config payloads during development with a JSON formatter tool before merging them into provider config.
Teams refactoring legacy boot code often adopt ideas from clean architecture in Laravel and Laravel best practices for clean code. For larger refactors, our custom software development service in Nepal covers provider audits on existing codebases.
Key Takeaways
- Keep
register()limited to container bindings and config merges; put routes, events, and views inboot(). - Defer providers that wrap heavy SDKs, but list every binding in
provides()or resolution silently fails. - Bind both interface and string alias when shipping packages so facades and constructor injection stay aligned.
- Never call
env()inside providers; always read values throughconfig()after caching. - Test providers with
config:cacheenabled locally before every production deploy. - Inspect
bootstrap/cache/services.phpwhen a provider works in development but disappears in production.
People Also Ask
What are service providers in Laravel used for?
They bootstrap application features by registering classes in the service container and connecting them to routes, events, views, policies, and middleware. Framework providers load the database, cache, and mail systems. Your own providers wire domain services like payment gateways or PDF engines.
Where are Laravel service providers registered?
Application providers live in bootstrap/providers.php from Laravel 11 onward. Package providers auto-register through the extra.laravel.providers key in composer.json. Optimized deployments compile the full list into bootstrap/cache/services.php.
Can you call other services inside register()?
You should not. Other providers may not have registered yet, which causes circular dependency errors. Resolve dependencies in boot() or inside lazy closures passed to bind() and singleton().
What is the difference between Laravel service providers and the service container?
The container stores and resolves objects. Service providers populate that container during application startup. Providers are configuration; the container is the runtime registry that controllers and jobs consume through injection.
Apply these patterns on your next Laravel project
Understanding service providers in Laravel changes how you structure packages, integrations, and deploy pipelines. Audit your existing providers for misplaced logic in register(). Profile boot time and defer anything not needed on every request. Test with cached config before you ship. If you are upgrading from Laravel 10 or 11, review the Laravel 12 migration guide for provider list changes. Need help untangling legacy providers on a production app? Contact us to discuss an architecture review, or reach out directly with your codebase questions.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

