What Actually Changed in Laravel 12
Laravel 12 is not a paradigm shift — it is a focused refinement. The headline changes are a slimmer default skeleton, a cleaner route file layout, a revised bootstrap/app.php contract, and the removal of several long-deprecated helpers. If you have kept up with 10 and 11, the upgrade is straightforward. If you skipped 11, read on carefully.
The Slim Skeleton
Laravel 11 introduced the condensed skeleton that dropped app/Http/Kernel.php and app/Providers/RouteServiceProvider.php. Laravel 12 doubles down: a fresh laravel new project ships with fewer stub files and a single bootstrap/app.php that owns middleware registration, exception handling, and routing in one place.
// bootstrap/app.php — Laravel 12 default
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
\App\Http\Middleware\HandleInertiaRequests::class,
]);
})
->withExceptions(function (Exceptions $exceptions) {
$exceptions->render(function (\App\Exceptions\DomainException $e, Request $request) {
return response()->json(['error' => $e->getMessage()], 422);
});
})
->create();
The withRouting call now accepts a then closure for anything dynamic — useful for module-based route loading without a custom service provider.
->withRouting(
web: __DIR__.'/../routes/web.php',
then: function () {
Route::middleware('api')
->prefix('webhooks')
->group(base_path('routes/webhooks.php'));
},
)
Structured Route Files by Convention
The routes/ directory now ships with web.php, api.php, console.php, and channels.php. Each is auto-loaded based on the withRouting declaration — no manual require or service provider needed. This is not new functionality, but the skeleton now enforces the pattern from day one.
For modular monoliths, the then closure is the idiomatic hook:
then: function () {
foreach (glob(base_path('modules/*/routes.php')) as $file) {
Route::middleware('web')->group($file);
}
},
Removed Deprecated Helpers
Several helpers deprecated in Laravel 9–11 are gone:
Str::replaceArray()— usestr_replacewith an array.Route::prefix()chained directly on the facade without a group closure.- The
dispatchNowalias on theBusfacade — usedispatchSync.
Run php artisan about and review your composer.json for packages that may still call these internally.
Upgrading from Laravel 11
1. Update composer.json
"laravel/framework": "^12.0"
Run composer update and address any immediate conflicts before touching application code.
2. Publish the new stubs
php artisan stub:publish
Compare your existing bootstrap/app.php against the new default. If you are on Laravel 11 already, the diff is minimal.
3. Audit deprecated usages
grep -rn 'dispatchNow\|replaceArray' app/ routes/
Replace each hit before deploying.
4. Check package compatibility
Filament v3/v4, Spatie packages, and most first-party packages had 12-compatible releases within days of the framework tag. Pin to their latest minor before upgrading the framework in production.
PHP Version Requirements
Laravel 12 requires PHP 8.2 at minimum, with full support for 8.3 and 8.4. If you are still on 8.1, upgrade PHP first — the framework will refuse to install.
Key Takeaways
bootstrap/app.phpis now the single source of truth for middleware, routing, and exception rendering.- The
withRouting(then:)closure is the clean hook for module-based or conditional route loading. - Several long-deprecated helpers are removed — audit with
grepbefore upgrading. - PHP 8.2+ is required; test on 8.3 to benefit from typed class constants and improved readonly semantics.
- The upgrade from Laravel 11 is low-risk; from Laravel 10 it requires the 11 skeleton migration first.