Saltar al contenido principal

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

Actualización con IA

También puedes automatizar la actualización con 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.
prompt
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.
{
  "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.
{
  "require": {
    "laravel/boost": "^2.0"
  }
}
Después, instala las dependencias con el siguiente comando.
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:
composer global update laravel/installer
Si usas la versión bundled de Laravel Herd, 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.
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é

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.
// 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.
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.
// 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.
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.
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'.
// 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).
$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.
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).
// 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.
// 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 (...).
// 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.
// 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

FuncionalidadAlternativa
Middleware VerifyCsrfTokenPreventRequestForgery
Middleware ValidateCsrfTokenPreventRequestForgery
JobAttempted::$exceptionOccurredJobAttempted::$exception
QueueBusy::$connectionQueueBusy::$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é.
// 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.
// 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.
'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.
// 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.
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é.
php artisan cache:clear

Referencias

Última modificación el 13 de julio de 2026