Comparison with Laravel 10 and earlier
Laravel 11 introduced the “Slim Application Skeleton,” a significant overhaul of the application structure. The biggest change is the consolidation of scattered configuration into a single place:bootstrap/app.php.
This change is intended for new projects. Upgrading an existing application from an older version leaves the previous structure fully functional.
New directory and file structure
Skeleton directory layout
bootstrap/app.php — the center of application configuration
app/Http/Kernel.php, app/Console/Kernel.php, and app/Exceptions/Handler.php.
bootstrap/providers.php — list of service providers
providers array inside config/app.php. They have now been separated into bootstrap/providers.php. Only AppServiceProvider is included by default.
Changes to the routes/ directory
api.php and channels.php do not exist by default. Generate them with Artisan commands when needed.
routes/console.php.
Removed files
Removal of app/Http/Kernel.php
Removal of app/Http/Kernel.php
The HTTP kernel was merged into the framework’s
Illuminate\Foundation\Http\Kernel. Middleware customization is now done via withMiddleware() in bootstrap/app.php.Removal of app/Console/Kernel.php
Removal of app/Console/Kernel.php
The two responsibilities of the console kernel were separated. Artisan commands placed in
app/Console/Commands/ are auto-discovered, and schedules are defined in routes/console.php.Removal of app/Exceptions/Handler.php
Removal of app/Exceptions/Handler.php
The exception handler was merged into the framework’s
Illuminate\Foundation\Exceptions\Handler. Customization is now done via withExceptions() in bootstrap/app.php.How Application::configure() works
Internal framework implementation
Application::configure() is a static method on Illuminate\Foundation\Application.
- Determines the application root directory from
basePath - Creates an
Applicationinstance - Wraps it in an
ApplicationBuilderand applies default configuration - Returns the
ApplicationBuilderinstance
withKernels(), withEvents(), withCommands(), and withProviders() are already called inside configure(). You do not need to call them again in bootstrap/app.php.
The method-chaining configuration flow
create() extracts the Application instance from the ApplicationBuilder. What bootstrap/app.php returns is this Application instance.
Request-to-boot flow
public/index.php is the entry point. It loads bootstrap/app.php to obtain the Application, after which the HTTP Kernel passes the request through the middleware pipeline and the router dispatches it to a controller.
Deep dive into ApplicationBuilder methods
withRouting() — internal route registration
AppRouteServiceProvider::loadRoutesUsing() and registers AppRouteServiceProvider during the application’s boot phase.
apiroutes automatically get theapimiddleware group and the/apiprefix- Passing a string to
healthauto-registers a health-check endpoint (default/up) - The
healthpath is excluded during maintenance mode (configured viaPreventRequestsDuringMaintenance::except()) apiis registered beforeweb— if you define routes with the same path for both, the API route takes precedence- Passing a string to
pagesenables Laravel Folio routing
withMiddleware() — middleware customization
withMiddleware() executes the callback after HttpKernel is resolved, using an afterResolving() hook. The Middleware object passed to the callback provides a rich set of customization methods.
withExceptions() — exception handling configuration
withExceptions() registers the framework’s Handler class as a singleton and then sets up the callback via afterResolving(). The callback receives an Exceptions wrapper object.
withProviders() — service provider registration
withProviders() is called by default inside Application::configure(), bootstrap/providers.php is loaded automatically. If you need to register additional providers, call withProviders() explicitly in bootstrap/app.php.
Other key methods
Design intent: why it works this way
”Code-first” configuration
In Laravel 10 and earlier,app/Http/Kernel.php used arrays to enumerate middleware, resembling a configuration file. This approach provided limited benefit from PHP’s type system and IDE support.
Laravel 11 switches to the callback style withMiddleware(function (Middleware $middleware) { ... }). This enables type completion and makes dynamic configuration using conditionals and loops feel natural.
From “convention over configuration” to “explicit configuration”
Makingapi.php opt-in removes the overhead of always loading the api middleware group for applications that do not use API routes. The philosophy is: if you do not use a feature, it should not exist by default.
Use of afterResolving() hooks
withMiddleware() and withExceptions() use afterResolving() to avoid ordering issues. The ApplicationBuilder methods are called before the application is fully bootstrapped, but the actual work (applying configuration to the kernel) is deferred until the kernel is first resolved.
Practical customization examples
Combining API and web routes
Middleware customization
Consolidating schedules in bootstrap/app.php
You can write schedules in routes/console.php, but using withSchedule() lets you keep everything in bootstrap/app.php.
Exception handling customization
Managing container bindings in bootstrap/app.php
For small applications, you can define simple bindings in bootstrap/app.php instead of AppServiceProvider.
Laravel boot process order
When a Laravel application starts, service providers and Application hooks fire in this order:- All ServiceProvider
register()methods run - Application
registered() - Application
booting() - All ServiceProvider
boot()methods run - Application
booted()
AppServiceProvider:
ApplicationBuilder’s registered(), booting(), and booted() methods simply register callbacks on the Application instance. You rarely need them in normal usage, but they enable advanced scenarios — for example, modifying a fully-booted Kernel via booted():
Next steps
Service container
Understand the service container that ApplicationBuilder uses internally.