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

# Guía de actualización de Laravel 12 a 13

> Se explican los pasos de actualización de Laravel 12 a Laravel 13, los cambios disruptivos, las funcionalidades obsoletas y los principales puntos destacados de las novedades.

## Introducción

Laravel 13 se lanzó en marzo de 2026. Esta guía explica los pasos para actualizar de Laravel 12.x a 13.x.

<Info>
  El tiempo estimado necesario para la actualización es de **unos 10 minutos**. Sin embargo, el impacto de los cambios disruptivos en tu aplicación varía según el tamaño y las funcionalidades utilizadas.
</Info>

### Actualización con IA

También puedes automatizar la actualización con [Laravel Boost](https://github.com/laravel/boost). Boost es un servidor MCP de primera parte que ofrece prompts de actualización paso a paso a los asistentes de IA. Tras instalarlo en una aplicación Laravel 12, puedes iniciar la actualización a Laravel 13 usando el slash command `/upgrade-laravel-v13` en Claude Code, Cursor, OpenCode, Gemini o VS Code. Este comando requiere `laravel/boost ^2.0`.

También en herramientas de IA que no admiten slash commands puedes ejecutar el mismo procedimiento de actualización consultando directamente el archivo de prompt. Pega el siguiente prompt tal cual en la IA.

```text prompt theme={null}
Lee el prompt que proporciona Laravel Boost y realiza el trabajo de actualización de Laravel 12 a 13.
https://raw.githubusercontent.com/laravel/boost/refs/heads/main/src/Mcp/Prompts/UpgradeLaravelv13/upgrade-laravel-v13.blade.php

- Refleja los cambios del esqueleto `laravel/laravel`. Consulta la rama 13.x, no master.
- Para `database/migrations/*_create_cache_table.php`, en lugar de modificarla, crea una nueva migración.
- En `config/session.php`, establece `serialization` en `php`. `'serialization' => 'php'`
- El cambio de Laravel 13 que más errores puede provocar en la app es el cambio de comportamiento de la caché; busca los usos de caché en el proyecto y, si es seguro permitirlos, actualiza `serializable_classes` en config/cache.php para permitirlos.

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

***

## Cambios por nivel de impacto

### Impacto: alto

* Actualización de dependencias
* Actualización del instalador de Laravel
* Prevención de request forgery (CSRF)

### Impacto: medio

* Configuración `serializable_classes` de caché
* Configuración `serialization` de sesión

### Impacto: bajo

* Prefijo de caché y nombre de cookie de sesión
* Serialización de colecciones de modelos
* `Container::call` y valores por defecto nullable de clase
* Prioridad de registro de rutas por dominio
* Payload de excepción en el evento `JobAttempted`
* Binding del callback de `extend` en Manager
* Query `DELETE` de MySQL (JOIN / ORDER BY / LIMIT)
* Nombre de la vista de paginación de Bootstrap
* Generación del nombre de tablas pivote polimórficas
* Renombrado de propiedad en el evento `QueueBusy`
* Reset entre tests de la factoría `Str`

***

## Pasos de actualización

### Actualización de dependencias

**Impacto: alto**

Actualiza las siguientes dependencias en `composer.json`.

```json theme={null}
{
  "require": {
    "laravel/framework": "^13.0",
    "laravel/tinker": "^3.0"
  },
  "require-dev": {
    "phpunit/phpunit": "^12.0",
    "pestphp/pest": "^4.0"
  }
}
```

Si usas Laravel Boost, actualízalo también.

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

Después, instala las dependencias con el siguiente comando.

```shell theme={null}
composer update
```

***

### Actualización del instalador de Laravel

**Impacto: alto**

Si creas nuevas aplicaciones de Laravel con el CLI del instalador, actualízalo a una versión compatible con Laravel 13.x.

Si lo instalaste con `composer global require`:

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

Si usas la versión bundled de [Laravel Herd](https://herd.laravel.com), actualiza el propio Herd a la última release.

***

## Cambios disruptivos (Breaking Changes)

### Seguridad

#### Prevención de request forgery

**Impacto: alto**

El middleware CSRF de Laravel se ha renombrado de `VerifyCsrfToken` a `PreventRequestForgery`. Además, se ha añadido validación del origen de la petición usando la cabecera `Sec-Fetch-Site`.

`VerifyCsrfToken` y `ValidateCsrfToken` permanecen como alias obsoletos, pero todos los lugares que los referencien directamente deben actualizarse a `PreventRequestForgery`. Presta especial atención si excluyes middleware en tests o definiciones de rutas.

```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]);
```

En la API de configuración de middleware también se puede usar `preventRequestForgery(...)`.

***

### Caché

#### Prefijo de caché y nombre de cookie de sesión

**Impacto: bajo**

Los prefijos por defecto de caché y de claves Redis en Laravel ahora usan sufijos separados por guiones. Además, el nombre por defecto de la cookie de sesión pasa a usar `Str::snake(...)`.

Muchas aplicaciones configuran estos valores explícitamente en los archivos de configuración, por lo que este cambio no las afecta. Solo se ven afectadas las aplicaciones que dependen de la configuración de fallback del framework.

```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';
```

Para mantener el comportamiento anterior, configúralo explícitamente en el archivo `.env`.

```ini theme={null}
CACHE_PREFIX=myapp_cache_
REDIS_PREFIX=myapp_database_
SESSION_COOKIE=myapp_session
```

#### Configuración `serializable_classes` de caché

**Impacto: medio**

Se ha añadido la opción `serializable_classes` en la configuración por defecto de `cache`, con valor predeterminado `false`. Con ello se previenen los ataques de deserialization gadget chain de PHP en caso de fuga de `APP_KEY`.

Si tu aplicación guarda intencionadamente objetos PHP en caché, hay que listar explícitamente las clases cuya deserialización se permite.

```php theme={null}
// config/cache.php
'serializable_classes' => [
    App\Data\CachedDashboardStats::class,
    App\Support\CachedPricingSnapshot::class,
],
```

Si venías deserializando objetos arbitrarios de caché, tienes que migrar a una lista blanca de clases explícita o a payloads de caché no-objeto (arrays, etc.).

#### Configuración `serialization` de sesión

**Impacto: medio**

En `config/session.php` del esqueleto de Laravel 13 (`laravel/laravel`) se ha añadido `'serialization' => 'json'`. Sin embargo, el valor por defecto interno del framework sigue siendo `php`.

<Warning>
  Si al aplicar los cambios del esqueleto con una herramienta de IA, en `config/session.php` se añade `'serialization' => 'json'`, este cambio conmuta el modo de serialización de la sesión y puede provocar errores en aplicaciones que guardan objetos PHP en la sesión.
</Warning>

La guía oficial de actualización no menciona el cambio de este ajuste. Esto se interpreta como que **no hace falta cambiarlo**. En Laravel, en actualizaciones a la versión inmediatamente anterior, a veces hay cambios que no se documentan en la guía de actualización porque no tienen impacto. Cuando se intenta actualizar años después y no hay información, uno se encuentra con problemas; por eso es importante dejar constancia extraoficial.

Para mantener el mismo comportamiento que hasta Laravel 12, indica explícitamente `'serialization' => 'php'`.

```php theme={null}
// config/session.php
'serialization' => 'php',
```

Si vas a habilitar la serialización JSON, confirma antes que no guardas objetos PHP en la sesión.

***

### Contenedor

#### `Container::call` y valores por defecto nullable de clase

**Impacto: bajo**

`Container::call` respeta ahora los valores por defecto de parámetros nullable de clase cuando no hay binding (coincide con el comportamiento introducido en Laravel 12 para la inyección de constructor).

```php theme={null}
$container->call(function (?Carbon $date = null) {
    return $date;
});

// Laravel <= 12.x: devuelve una instancia de Carbon
// Laravel >= 13.x: devuelve null
```

***

### Base de datos

#### Query `DELETE` de MySQL

**Impacto: bajo**

Laravel ahora compila queries `DELETE ... JOIN` completas con `ORDER BY` y `LIMIT` en la gramática de MySQL.

En versiones anteriores, en un DELETE con JOIN podían ignorarse las cláusulas `ORDER BY` / `LIMIT`. En Laravel 13 esas cláusulas se incluyen en el SQL generado. Como resultado, en algunos motores de base de datos que no soportan esta sintaxis puede producirse una `QueryException`.

***

### Eloquent

#### Generación del nombre de tablas pivote polimórficas

**Impacto: bajo**

Cuando se infiere el nombre de tabla de un modelo pivote polimórfico que usa una clase de pivote personalizada, Laravel ahora genera el nombre en plural.

Si dependías del nombre singular anterior, indica explícitamente el nombre de la tabla en el modelo pivote.

```php theme={null}
class RoleUser extends MorphPivot
{
    protected $table = 'role_user'; // Indicarlo explícitamente
}
```

#### Serialización de colecciones de modelos

**Impacto: bajo**

Al serializar y restaurar colecciones de modelos Eloquent (por ejemplo en jobs encolados), las relaciones eager cargadas se restauran ahora en los modelos.

Si tenías código que dependía de que las relaciones no existieran tras la deserialización, tendrás que corregirlo.

***

### Cola

#### Payload de excepción en el evento `JobAttempted`

**Impacto: bajo**

En lugar de la anterior propiedad booleana `$exceptionOccurred`, el evento `Illuminate\Queue\Events\JobAttempted` expone ahora en `$exception` el objeto excepción (o `null`).

```php theme={null}
// Laravel <= 12.x
if ($event->exceptionOccurred) {
    // Se ha producido una excepción
}

// Laravel >= 13.x
if ($event->exception !== null) {
    // Se ha producido una excepción
    $exception = $event->exception;
}
```

#### Renombrado de propiedad en el evento `QueueBusy`

**Impacto: bajo**

La propiedad `$connection` del evento `Illuminate\Queue\Events\QueueBusy` se ha renombrado a `$connectionName` por coherencia con otros eventos de cola.

```php theme={null}
// Laravel <= 12.x
$event->connection;

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

***

### Enrutamiento

#### Prioridad de registro de rutas por dominio

**Impacto: bajo**

Las rutas con un dominio explícito tienen ahora prioridad frente a las rutas sin dominio en el matching.

Con esto, las rutas de subdominio catch-all funcionan de forma coherente aunque se hayan registrado antes rutas sin dominio.

***

### Soporte

#### Binding del callback de `extend` en Manager

**Impacto: bajo**

Los closures de drivers personalizados registrados con el método `extend` de Manager se enlazan ahora a la instancia del manager.

Antes, si en esos callbacks se estaba referenciando otro objeto (por ejemplo la instancia de service provider) como `$this`, tendrás que mover el valor a captura de closure con `use (...)`.

```php theme={null}
// Laravel <= 12.x
Manager::extend('custom', function ($app) {
    return $this->createCustomDriver($app); // $this era el service provider
});

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

#### Reset entre tests de la factoría `Str`

**Impacto: bajo**

Laravel ahora resetea las factorías `Str` personalizadas en el teardown de los tests.

Si dependías de que las factorías personalizadas de UUID / ULID / string aleatoria persistieran entre métodos de test, configúralas en cada test relacionado o en un hook de setup.

***

### Vistas

#### Nombre de la vista de paginación de Bootstrap

**Impacto: bajo**

Los nombres internos de las vistas de paginación por defecto de Bootstrap 3 son ahora explícitos.

```php theme={null}
// Laravel <= 12.x
pagination::default
pagination::simple-default

// Laravel >= 13.x
pagination::bootstrap-3
pagination::simple-bootstrap-3
```

Si referenciabas directamente los nombres antiguos de vista de paginación, actualízalos.

***

## Funcionalidades obsoletas

| Funcionalidad                      | Alternativa                  |
| ---------------------------------- | ---------------------------- |
| Middleware `VerifyCsrfToken`       | `PreventRequestForgery`      |
| Middleware `ValidateCsrfToken`     | `PreventRequestForgery`      |
| `JobAttempted::$exceptionOccurred` | `JobAttempted::$exception`   |
| `QueueBusy::$connection`           | `QueueBusy::$connectionName` |

***

## Añadidos a contratos

**Impacto: muy bajo**

Solo afecta si tienes implementaciones personalizadas.

### Contract `Dispatcher`

Se ha añadido el método `dispatchAfterResponse($command, $handler = null)` al contract `Illuminate\Contracts\Bus\Dispatcher`.

### Contract `ResponseFactory`

Se ha añadido la firma `eventStream` al contract `Illuminate\Contracts\Routing\ResponseFactory`.

### Contract `MustVerifyEmail`

Se ha añadido `markEmailAsUnverified()` al contract `Illuminate\Contracts\Auth\MustVerifyEmail`.

### Contract `Queue`

Se han añadido al contract `Illuminate\Contracts\Queue\Queue` los siguientes métodos de inspección del tamaño de la cola (antes solo se declaraban en docblocks).

* `pendingSize`
* `delayedSize`
* `reservedSize`
* `creationTimeOfOldestPendingJob`

### Contracts `Store` / `Repository`

Se ha añadido el método `touch` para extender el TTL en los contracts de caché.

```php theme={null}
// Illuminate\Contracts\Cache\Store
public function touch($key, $seconds);
```

***

## Puntos destacados de las novedades

### Actualización asistida por IA (Laravel Boost)

Laravel Boost es un servidor MCP oficial. Se puede semi-automatizar la actualización con el comando `/upgrade-laravel-v13`, integrándose con los editores de IA.

### Validación del origen de la petición con la cabecera `Sec-Fetch-Site`

El middleware `PreventRequestForgery` realiza una verificación adicional de origen usando la cabecera `Sec-Fetch-Site`. Con ello se refuerza la protección CSRF.

### Deserialización segura de la caché

Con la configuración `serializable_classes`, solo se deserializan las clases permitidas. Mejora la seguridad frente a ataques de deserialización de PHP.

### `eventStream` para SSE (Server-Sent Events)

Se ha añadido `eventStream` al contract `ResponseFactory`, mejorando el soporte de Server-Sent Events.

### Mejora de la visibilidad de las colas

Con los métodos `pendingSize`, `delayedSize`, `reservedSize`, etc. del contract `Queue`, es posible monitorizar con mayor detalle el estado de las colas.

***

## Problemas frecuentes durante la migración y sus soluciones

### Problema: los tests relacionados con CSRF fallan

**Síntoma:** los tests que referencian `VerifyCsrfToken` fallan con un error de clase no encontrada.

**Solución:** actualiza todas las referencias a `PreventRequestForgery`.

```php theme={null}
// Before
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
->withoutMiddleware([VerifyCsrfToken::class]);

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

### Problema: no se restauran objetos desde la caché

**Síntoma:** los datos obtenidos de la caché son `null` o se produce una `UnserializationFailedException`.

**Solución:** añade las clases usadas a `serializable_classes` en `config/cache.php`, o convierte los valores cacheados a array.

```php theme={null}
'serializable_classes' => [
    App\Models\User::class,
    App\Data\SomeData::class,
],
```

### Problema: el listener `JobAttempted` no funciona

**Síntoma:** `$event->exceptionOccurred` es `null` o produce error de indefinido.

**Solución:** cámbialo a `$event->exception !== null`.

```php theme={null}
// Before
if ($event->exceptionOccurred) { ... }

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

### Problema: la sesión se invalida

**Síntoma:** tras la actualización, los usuarios son deslogueados.

**Solución:** el nombre por defecto de la cookie de sesión ha cambiado. Establece explícitamente `SESSION_COOKIE` en `.env` para mantener el valor anterior.

```ini theme={null}
SESSION_COOKIE=laravel_session
```

### Problema: no se encuentran claves de caché

**Síntoma:** aumentan los cache miss tras la actualización.

**Solución:** el prefijo de caché ha cambiado. Establece `CACHE_PREFIX` en `.env` o limpia la caché.

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

***

## Referencias

* [Guía oficial de actualización (inglés)](https://laravel.com/docs/13.x/upgrade)
* [Diff del repositorio laravel/laravel (12.x → 13.x)](https://github.com/laravel/laravel/compare/12.x...13.x)
* [Laravel Shift](https://laravelshift.com): servicio comunitario que automatiza la actualización
* [Laravel Boost](https://github.com/laravel/boost): servidor MCP para actualización asistida por IA


## Related topics

- [Resumen de las novedades de Laravel 13](/es/blog/laravel-13-new-features.md)
- [Guía de actualización de Laravel 11 a 12](/es/blog/upgrade-11-to-12.md)
- [Actualización de Laravel 8 a 9](/es/blog/upgrade-8-to-9.md)
- [Actualización de Laravel 9 a 10](/es/blog/upgrade-9-to-10.md)
- [Actualización de Laravel 10 a 11](/es/blog/upgrade-10-to-11.md)
