> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Upgrade guide: Laravel 12 to 13

> Upgrade steps, breaking changes, deprecations, and new feature highlights when moving from Laravel 12 to 13.

## Introduction

Laravel 13 was released in March 2026. This guide walks through upgrading from Laravel 12.x to 13.x.

<Info>
  Estimated time to upgrade is **about 10 minutes**. Actual impact from breaking changes depends on your app's size and features you use.
</Info>

### AI-assisted upgrade

You can also automate the upgrade using [Laravel Boost](https://github.com/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.

```text prompt theme={null}
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`.

```json theme={null}
{
  "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.

```json theme={null}
{
  "require": {
    "laravel/boost": "^2.0"
  }
}
```

Then install dependencies:

```shell theme={null}
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`:

```shell theme={null}
composer global update laravel/installer
```

If you use the [Laravel Herd](https://herd.laravel.com) 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.

```php theme={null}
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

#### Cache prefixes and session cookie name

**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.

```php theme={null}
// 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`.

```ini theme={null}
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.

```php theme={null}
// 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`.

<Warning>
  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.
</Warning>

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.

```php theme={null}
// 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).

```php theme={null}
$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.

```php theme={null}
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.

```php theme={null}
// 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.

```php theme={null}
// 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 (...)`.

```php theme={null}
// 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.

```php theme={null}
// 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

| Feature                            | Replacement                  |
| ---------------------------------- | ---------------------------- |
| `VerifyCsrfToken` middleware       | `PreventRequestForgery`      |
| `ValidateCsrfToken` middleware     | `PreventRequestForgery`      |
| `JobAttempted::$exceptionOccurred` | `JobAttempted::$exception`   |
| `QueueBusy::$connection`           | `QueueBusy::$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.

```php theme={null}
// 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

### Issue: CSRF-related tests fail

**Symptom:** Tests referencing `VerifyCsrfToken` fail with a class-not-found error.

**Fix:** Update all references to `PreventRequestForgery`.

```php theme={null}
// 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.

```php theme={null}
'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`.

```php theme={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.

```ini theme={null}
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.

```shell theme={null}
php artisan cache:clear
```

***

## References

* [Official upgrade guide (English)](https://laravel.com/docs/13.x/upgrade)
* [laravel/laravel diff (12.x → 13.x)](https://github.com/laravel/laravel/compare/12.x...13.x)
* [Laravel Shift](https://laravelshift.com) — community service to automate upgrades
* [Laravel Boost](https://github.com/laravel/boost) — MCP server for AI-assisted upgrades


## Related topics

- [Upgrade guide: Laravel 11 to 12](/en/blog/upgrade-11-to-12.md)
- [Laravel 13 new features overview](/en/blog/laravel-13-new-features.md)
- [Package CHANGELOG and Release Management](/en/advanced/package-changelog.md)
- [Package Version Compatibility Management](/en/advanced/package-versioning.md)
- [Upgrading from Laravel 9 to 10](/en/blog/upgrade-9-to-10.md)
