Skip to main content

Introduction

Laravel 13 was released in March 2026. This guide walks through upgrading from Laravel 12.x to 13.x.
Estimated time to upgrade is about 10 minutes. Actual impact from breaking changes depends on your app’s size and features you use.

AI-assisted upgrade

You can also automate the upgrade using Laravel Boost. Boost is a first-party MCP server that provides staged upgrade prompts to AI assistants. After installing it into a Laravel 12 application, you can start the upgrade to Laravel 13 by using the /upgrade-laravel-v13 slash command in Claude Code, Cursor, OpenCode, Gemini, or VS Code. This command requires laravel/boost ^2.0. For AI tools that don’t support slash commands, you can execute the same upgrade steps by referring to the prompt file directly. Paste the following prompt to your AI as-is.
prompt
Read the prompt provided by Laravel Boost and perform the upgrade from Laravel 12 to 13.
https://raw.githubusercontent.com/laravel/boost/refs/heads/main/src/Mcp/Prompts/UpgradeLaravelv13/upgrade-laravel-v13.blade.php

- Reflect the changes in the `laravel/laravel` skeleton. Refer to the 13.x branch, not master.
- Do not modify `database/migrations/*_create_cache_table.php` in place—create a new migration instead.
- Set `config/session.php`'s `serialization` to `php`: `'serialization' => 'php'`
- The change that most often breaks apps on Laravel 13 is the cache serialization behavior—search cache usages across the project and, if it looks safe, update `serializable_classes` in `config/cache.php` accordingly.

'serializable_classes' => [
    App\Data\CachedDashboardStats::class,
    App\Support\CachedPricingSnapshot::class,
    Illuminate\Support\Collection::class,
    Illuminate\Database\Eloquent\Collection::class,
],

Changes by impact level

Impact: high

  • Dependency updates
  • Laravel installer update
  • Request forgery protection (CSRF)

Impact: medium

  • Cache serializable_classes setting
  • Session serialization setting

Impact: low

  • Cache prefixes and session cookie name
  • Collection model serialization
  • Container::call and nullable class defaults
  • Domain route registration priority
  • JobAttempted event exception payload
  • Manager extend callback binding
  • MySQL DELETE queries (JOIN / ORDER BY / LIMIT)
  • Pagination Bootstrap view names
  • Polymorphic pivot table name generation
  • QueueBusy event property rename
  • Cross-test Str factory reset

Upgrade steps

Update dependencies

Impact: high Update the following dependencies in composer.json.
{
  "require": {
    "laravel/framework": "^13.0",
    "laravel/tinker": "^3.0"
  },
  "require-dev": {
    "phpunit/phpunit": "^12.0",
    "pestphp/pest": "^4.0"
  }
}
If you use Laravel Boost, update it too.
{
  "require": {
    "laravel/boost": "^2.0"
  }
}
Then install dependencies:
composer update

Update the Laravel installer

Impact: high If you use the Laravel installer CLI to create new Laravel apps, update it to the version that supports Laravel 13.x. If installed via composer global require:
composer global update laravel/installer
If you use the Laravel Herd bundled version, update Herd itself to its latest release.

Breaking changes

Security

Request forgery protection

Impact: high Laravel’s CSRF middleware has been renamed from VerifyCsrfToken to PreventRequestForgery. Origin validation via the Sec-Fetch-Site header has also been added. VerifyCsrfToken and ValidateCsrfToken remain as deprecated aliases, but any direct references should be updated to PreventRequestForgery. Be especially careful when excluding the middleware in tests or route definitions.
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;

// Laravel <= 12.x
->withoutMiddleware([VerifyCsrfToken::class]);

// Laravel >= 13.x
->withoutMiddleware([PreventRequestForgery::class]);
preventRequestForgery(...) is also now available in the middleware configuration API.

Cache

Impact: low Laravel’s default cache and Redis key prefixes now use hyphen-separated suffixes. The default session cookie name now uses Str::snake(...). Most apps set these explicitly in config, so they aren’t affected. Only apps that depend on the framework’s fallback settings are affected.
// Laravel <= 12.x
Str::slug((string) env('APP_NAME', 'laravel'), '_').'_cache_';
Str::slug((string) env('APP_NAME', 'laravel'), '_').'_database_';
Str::slug((string) env('APP_NAME', 'laravel'), '_').'_session';

// Laravel >= 13.x
Str::slug((string) env('APP_NAME', 'laravel')).'-cache-';
Str::slug((string) env('APP_NAME', 'laravel')).'-database-';
Str::snake((string) env('APP_NAME', 'laravel')).'_session';
To keep the previous behavior, set them explicitly in .env.
CACHE_PREFIX=myapp_cache_
REDIS_PREFIX=myapp_database_
SESSION_COOKIE=myapp_session

Cache serializable_classes setting

Impact: medium A serializable_classes option has been added to the default cache configuration, and the default is false. This mitigates PHP deserialization gadget chain attacks if APP_KEY is leaked. If your app intentionally stores PHP objects in the cache, you must explicitly list the classes allowed for deserialization.
// config/cache.php
'serializable_classes' => [
    App\Data\CachedDashboardStats::class,
    App\Support\CachedPricingSnapshot::class,
],
If you were deserializing arbitrary cache objects, you need to migrate to explicit class allow lists or to non-object cache payloads (arrays, etc.).

Session serialization setting

Impact: medium The Laravel 13 skeleton (laravel/laravel) adds 'serialization' => 'json' in config/session.php. However, the internal framework default remains php.
If you apply the skeleton changes as-is via an AI tool, 'serialization' => 'json' may be added to config/session.php. Because this switches the session serialization method, it can cause errors for apps that store PHP objects in the session.
The official upgrade guide doesn’t mention this setting change. This likely means you don’t have to change it. Laravel occasionally has changes that aren’t documented in the upgrade guide because upgrading from the immediately preceding version isn’t affected. When someone tries to upgrade years later without information, they get stuck—so unofficial records matter. To keep the same behavior as Laravel 12 and earlier, set 'serialization' => 'php' explicitly.
// config/session.php
'serialization' => 'php',
To enable JSON serialization, first verify that no PHP objects are stored in the session.

Container

Container::call and nullable class defaults

Impact: low Container::call now respects the default value of a nullable class parameter when no binding exists (matching the behavior introduced for constructor injection in Laravel 12).
$container->call(function (?Carbon $date = null) {
    return $date;
});

// Laravel <= 12.x: returns a Carbon instance
// Laravel >= 13.x: returns null

Database

MySQL DELETE queries

Impact: low Laravel now compiles a full DELETE ... JOIN query including ORDER BY and LIMIT for MySQL grammar. In previous versions, ORDER BY / LIMIT clauses could be ignored on DELETEs with JOINs. In Laravel 13, those clauses are included in the generated SQL. As a result, some database engines that don’t support this syntax may throw a QueryException.

Eloquent

Polymorphic pivot table name generation

Impact: low When inferring the table name of polymorphic pivot models that use a custom pivot model class, Laravel now generates a plural name. If you relied on the previous singular inferred name, define the table name explicitly on the pivot model.
class RoleUser extends MorphPivot
{
    protected $table = 'role_user'; // Specify explicitly
}

Collection model serialization

Impact: low When Eloquent model collections are serialized and restored (e.g. in queued jobs), eager-loaded relationships are now restored for the models. If you had code that relied on relationships being absent after deserialization, fixes are required.

Queue

JobAttempted event exception payload

Impact: low The Illuminate\Queue\Events\JobAttempted event now exposes the exception object (or null) via $exception, in place of the previous boolean $exceptionOccurred property.
// Laravel <= 12.x
if ($event->exceptionOccurred) {
    // An exception occurred
}

// Laravel >= 13.x
if ($event->exception !== null) {
    // An exception occurred
    $exception = $event->exception;
}

QueueBusy event property rename

Impact: low The Illuminate\Queue\Events\QueueBusy event property $connection has been renamed to $connectionName for consistency with other queue events.
// Laravel <= 12.x
$event->connection;

// Laravel >= 13.x
$event->connectionName;

Routing

Domain route registration priority

Impact: low Routes with an explicit domain now take priority over non-domain routes in route matching. This ensures catch-all subdomain routes behave consistently even when non-domain routes are registered first.

Support

Manager extend callback binding

Impact: low Custom driver closures registered via a Manager’s extend method are now bound to the manager instance. If these callbacks previously referenced another object (like a service provider instance) as $this, you’ll need to move the value into the closure capture with use (...).
// Laravel <= 12.x
Manager::extend('custom', function ($app) {
    return $this->createCustomDriver($app); // $this is the service provider
});

// Laravel >= 13.x
$provider = $this;
Manager::extend('custom', function ($app) use ($provider) {
    return $provider->createCustomDriver($app);
});

Cross-test Str factory reset

Impact: low Laravel now resets custom Str factories during test tear-down. If you relied on custom UUID / ULID / random string factories persisting across test methods, set them in each relevant test or a setup hook.

Views

Pagination Bootstrap view names

Impact: low The internal pagination view names for the Bootstrap 3 defaults are now explicit.
// Laravel <= 12.x
pagination::default
pagination::simple-default

// Laravel >= 13.x
pagination::bootstrap-3
pagination::simple-bootstrap-3
Update any direct references to the old pagination view names.

Deprecated features

FeatureReplacement
VerifyCsrfToken middlewarePreventRequestForgery
ValidateCsrfToken middlewarePreventRequestForgery
JobAttempted::$exceptionOccurredJobAttempted::$exception
QueueBusy::$connectionQueueBusy::$connectionName

Added contract methods

Impact: very low Only affects you if you have custom implementations.

Dispatcher contract

The Illuminate\Contracts\Bus\Dispatcher contract adds dispatchAfterResponse($command, $handler = null).

ResponseFactory contract

The Illuminate\Contracts\Routing\ResponseFactory contract adds an eventStream signature.

MustVerifyEmail contract

The Illuminate\Contracts\Auth\MustVerifyEmail contract adds markEmailAsUnverified().

Queue contract

The Illuminate\Contracts\Queue\Queue contract adds the following queue-size inspection methods (previously declared only in docblocks).
  • pendingSize
  • delayedSize
  • reservedSize
  • creationTimeOfOldestPendingJob

Store / Repository contract

A touch method has been added to the cache contracts for extending TTLs.
// Illuminate\Contracts\Cache\Store
public function touch($key, $seconds);

New feature highlights

AI-assisted upgrade (Laravel Boost)

Laravel Boost is the official MCP server. It integrates with AI editors so you can semi-automate the upgrade with the /upgrade-laravel-v13 command.

Origin validation via Sec-Fetch-Site

The PreventRequestForgery middleware performs additional origin validation using the Sec-Fetch-Site header, strengthening CSRF protection.

Safe cache deserialization

With serializable_classes configuration, only allowed classes are deserialized. Security against PHP deserialization attacks is improved.

SSE (Server-Sent Events) eventStream

eventStream has been added to the ResponseFactory contract, improving Server-Sent Events support.

Queue visibility improvements

Methods on the Queue contract like pendingSize, delayedSize, and reservedSize let you monitor queue state more granularly.

Common migration issues and fixes

Symptom: Tests referencing VerifyCsrfToken fail with a class-not-found error. Fix: Update all references to PreventRequestForgery.
// Before
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
->withoutMiddleware([VerifyCsrfToken::class]);

// After
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery;
->withoutMiddleware([PreventRequestForgery::class]);

Issue: Objects can’t be restored from the cache

Symptom: Data fetched from the cache is null, or an UnserializationFailedException is thrown. Fix: Add the classes you use to serializable_classes in config/cache.php, or convert cached values to arrays.
'serializable_classes' => [
    App\Models\User::class,
    App\Data\SomeData::class,
],

Issue: JobAttempted listeners break

Symptom: $event->exceptionOccurred is null or undefined. Fix: Change to $event->exception !== null.
// Before
if ($event->exceptionOccurred) { ... }

// After
if ($event->exception !== null) { ... }

Issue: Sessions are invalidated

Symptom: Users are logged out after the upgrade. Fix: The default session cookie name has changed. Explicitly set SESSION_COOKIE in .env to keep the previous value.
SESSION_COOKIE=laravel_session

Issue: Cache keys can’t be found

Symptom: Cache misses increase after the upgrade. Fix: The cache prefix has changed. Set CACHE_PREFIX in .env or clear the cache.
php artisan cache:clear

References

Last modified on July 13, 2026