Skip to main content

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

This single file configures routing, middleware, and exception handling. In Laravel 10 and earlier, these settings were spread across app/Http/Kernel.php, app/Console/Kernel.php, and app/Exceptions/Handler.php.

bootstrap/providers.php — list of service providers

This file is the registration point for service providers. In Laravel 10, providers were listed in the providers array inside config/app.php. They have now been separated into bootstrap/providers.php. Only AppServiceProvider is included by default.
When you install a package via composer require, it may automatically update bootstrap/providers.php. While config/app.php is still referenced, bootstrap/providers.php is the recommended location for new registrations.

Changes to the routes/ directory

api.php and channels.php do not exist by default. Generate them with Artisan commands when needed.
You can also define schedules in routes/console.php.

Removed files

The HTTP kernel was merged into the framework’s Illuminate\Foundation\Http\Kernel. Middleware customization is now done via withMiddleware() in bootstrap/app.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.
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.
This method does the following:
  1. Determines the application root directory from basePath
  2. Creates an Application instance
  3. Wraps it in an ApplicationBuilder and applies default configuration
  4. Returns the ApplicationBuilder instance
The important detail is that 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

Calling 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

Internally, it registers a callback with AppRouteServiceProvider::loadRoutesUsing() and registers AppRouteServiceProvider during the application’s boot phase.
Key points:
  • api routes automatically get the api middleware group and the /api prefix
  • Passing a string to health auto-registers a health-check endpoint (default /up)
  • The health path is excluded during maintenance mode (configured via PreventRequestsDuringMaintenance::except())
  • api is registered before web — if you define routes with the same path for both, the API route takes precedence
  • Passing a string to pages enables 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

Because 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.
Passing withBootstrapProviders: false prevents bootstrap/providers.php from being loaded. Omit this option unless you have a specific reason to do so.

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”

Making api.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:
  1. All ServiceProvider register() methods run
  2. Application registered()
  3. Application booting()
  4. All ServiceProvider boot() methods run
  5. Application booted()
You can verify the order yourself by adding the following code to 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.
Last modified on May 4, 2026