What Laravel 13 Actually Changes
Laravel 13 is a focused release. Rather than a sweeping API redesign, it tightens the framework's internals, promotes PHP 8.3 features to first-class status, and ships a handful of helpers that remove common boilerplate. The upgrade path from Laravel 12 is intentionally narrow — most apps need only a few targeted changes.
Bootstrapping and Application Lifecycle
The Application bootstrap sequence now exposes a dedicated booting / booted hook pair directly on the application instance, giving you a cleaner place to register deferred work without abusing service providers.
// bootstrap/app.php
return Application::configure(basePath: dirname(__DIR__))
->withRouting(web: __DIR__.'/../routes/web.php')
->booting(function (Application $app) {
// Runs before any provider boot() is called.
$app->bind(ReportFormatter::class, JsonReportFormatter::class);
})
->booted(function (Application $app) {
// All providers are booted; safe to resolve.
$app->make(MetricsCollector::class)->register();
})
->create();
This replaces the pattern of creating a throwaway service provider just to hook into the lifecycle.
New Helpers Worth Knowing
rescue_unless
The inverse of rescue(). It runs the callback only when the condition is falsy, swallowing exceptions and returning a default — useful for optional third-party calls:
$price = rescue_unless(
condition: $this->cache->has('price:'.$sku),
callback: fn () => $this->pricingApi->fetch($sku),
rescue: null
);
str()->toBase64url() and fromBase64url()
URL-safe Base64 encoding lands on the Stringable fluent API, removing the need for manual strtr gymnastics:
$token = str($payload)->toBase64url();
$decoded = str($token)->fromBase64url();
Collection::groupByMany()
Groups a collection by multiple keys in a single pass, returning a nested structure:
$grouped = $orders->groupByMany(['region', 'status']);
// $grouped['EU']['pending'] => Collection of orders
PHP 8.3 Integrations
Laravel 13 requires PHP 8.2 minimum and is optimised for 8.3. Typed class constants are now used throughout the framework's own codebase, and the json_validate() built-in replaces several internal json_decode + error-check patterns. Your own code can follow the same idiom:
// Before
$valid = json_decode($raw, true) !== null && json_last_error() === JSON_ERROR_NONE;
// After (PHP 8.3)
$valid = json_validate($raw);
Readonly class promotion is also used in new stubs, so generated DTOs and form requests lean on readonly properties by default.
Breaking Changes to Watch
Illuminate\Http\Client\Response::json() strict mode
Passing an invalid JSON body no longer silently returns null; it throws JsonException. Wrap legacy HTTP client calls:
try {
$data = Http::get($url)->json();
} catch (\JsonException $e) {
Log::warning('Malformed API response', ['url' => $url]);
$data = [];
}
Removed: $loop variable in Blade @each
The $loop magic variable inside @each directives was deprecated in Laravel 12 and is removed in 13. Replace @each with @foreach to retain loop metadata.
Queue after_commit default
The after_commit flag on dispatched jobs now defaults to true when a database transaction is active. If you rely on jobs firing mid-transaction for testing or side-effects, set public bool $afterCommit = false explicitly on the job class.
Upgrade Checklist
- Run
composer require laravel/framework:^13.0and fix any immediate constraint failures. - Search for
@eachin Blade templates and convert to@foreach. - Audit HTTP client calls that parse JSON — add
JsonExceptionhandling. - Review jobs that dispatch inside transactions; verify
$afterCommitintent. - Update
phpunit.xmlto the Laravel 13 stub if you are on PHPUnit 11. - Run
php artisan config:clear && php artisan cache:clearafter deployment.
Key Takeaways
- The new
booting/bootedhooks onApplicationreduce throwaway service providers. rescue_unless,toBase64url, andgroupByManycover real day-to-day gaps.Http::json()now throws on bad JSON — handle it explicitly.@eachis gone;after_commitdefaults changed for queued jobs.- PHP 8.3 is the sweet spot;
json_validate()and readonly properties are first-class.