What Actually Changed in Laravel 12
Laravel 12 is a focused release. Rather than sweeping architectural shifts, it doubles down on developer ergonomics, tightens the application skeleton, and formalises patterns that were previously left to convention. If you are running Laravel 11 in production, the upgrade is low-risk — but there are a handful of decisions worth understanding before you composer update.
Revamped Starter Kits
The most visible change is the complete overhaul of the first-party starter kits. The old Breeze and Jetstream scaffolding has been replaced by a unified laravel/starter-kit system that ships with three official stacks: React, Vue, and Livewire, each backed by Inertia v2 or Livewire v3 respectively.
laravel new my-app --kit=react
laravel new my-app --kit=livewire
The kits are now proper Composer packages rather than stub-copied files, which means you get upstream fixes without re-scaffolding. Authentication views, middleware, and route registration are all published on demand:
php artisan kit:publish --views
For existing apps, there is no forced migration — Breeze still installs fine — but new projects should prefer the new kits.
Per-Request Context Propagation
Laravel 12 promotes the Context facade (introduced experimentally in 11.x) to a first-class feature with guaranteed per-request isolation under Octane and queue workers.
use Illuminate\Support\Facades\Context;
// In a middleware
Context::add('request_id', (string) Str::uuid());
Context::add('tenant_id', $request->user()?->tenant_id);
// Anywhere downstream — including queued jobs that inherit context
$requestId = Context::get('request_id');
Queued jobs now automatically receive a snapshot of the context at dispatch time. You opt out per-job if you need a clean slate:
class HeavyReport implements ShouldQueue
{
use WithoutContextSnapshot; // new trait
}
This is a significant win for distributed tracing and multi-tenant logging without reaching for a global singleton.
New and Refined Helpers
str()->wrap() — wraps a string with a prefix and optional suffix:
Str::of('world')->wrap('Hello, ', '!'); // "Hello, world!"
Arr::from() — a null-safe alternative to (array) casting that handles Arrayable, JsonSerializable, and plain scalars uniformly:
$items = Arr::from($collection); // no more (array) casting surprises
Number::spellOut() — converts integers to English words, useful for invoice generation:
Number::spellOut(1042); // "one thousand and forty-two"
Upgrade Notes
PHP requirement: Laravel 12 requires PHP 8.2 minimum. PHP 8.1 is dropped.
Skeleton changes: The bootstrap/app.php fluent API introduced in Laravel 11 is unchanged, but config/app.php loses several keys (aliases, providers) that are now auto-discovered. If you reference them programmatically, switch to app()->getProviders() or tagged bindings.
Removed deprecations:
Route::prefix()chained without a closure now throws — wrap your grouped routes properly.$request->validate()no longer accepts aValidatorinstance directly; use$this->validate()in controllers or construct the validator explicitly.
Composer update path:
composer require laravel/framework:^12.0 --update-with-dependencies
php artisan migrate
php artisan config:clear
Run php artisan about after upgrading — it now surfaces the active starter kit, context driver, and queue connection in a single view.
Takeaways
- The new starter kit system is package-based; prefer it for greenfield projects.
Contextpropagation to queued jobs is automatic in Laravel 12 — use it for tracing and tenant isolation.- PHP 8.1 support is gone; audit your dependencies before upgrading.
Arr::from(),str()->wrap(), andNumber::spellOut()are small but genuinely useful additions.- The upgrade from Laravel 11 is straightforward if you have already adopted the 11.x skeleton conventions.