Visión general
Al crear un proyecto Laravel, el manejo de errores y excepciones ya viene configurado.
Personalízalo con el método withExceptions en bootstrap/app.php.
Flujo de manejo de excepciones
Cómo transcurre el flujo desde que se lanza una excepción hasta que se devuelve una respuesta al cliente.
// bootstrap/app.php
use Illuminate\Foundation\ Application ;
use Illuminate\Foundation\Configuration\ Exceptions ;
return Application :: configure ( basePath : dirname ( __DIR__ ))
-> withExceptions ( function ( Exceptions $exceptions ) : void {
// Configura aquí el reporte y renderizado de excepciones
}) -> create ();
El objeto $exceptions que recibe la closure de withExceptions es una instancia de Illuminate\Foundation\Configuration\Exceptions que gestiona el manejo de excepciones de toda la aplicación.
Configuración de debug
La opción debug de config/app.php controla el nivel de detalle mostrado.
Por defecto usa el valor de APP_DEBUG en .env.
# Desarrollo local
APP_DEBUG =true
# Producción
APP_DEBUG =false
En producción, deja siempre APP_DEBUG=false. Si se queda en true, hay riesgo de exponer información sensible al usuario final.
Reporte de excepciones
Reportar una excepción es registrarla en el log o enviarla a servicios externos como Sentry o Flare .
Por defecto se registra en el log según la configuración de config/logging.php.
Callback personalizado de reporte
Para reportar según el tipo de excepción, pasa una closure a report.
Laravel detecta el tipo por el type-hint de la closure.
use App\Exceptions\ InvalidOrderException ;
-> withExceptions ( function ( Exceptions $exceptions ) : void {
$exceptions -> report ( function ( InvalidOrderException $e ) {
// Notificar a un servicio externo, etc.
});
})
Aun registrando un callback personalizado, el logging por defecto continúa.
Para cortar la propagación, llama a stop() o devuelve false.
-> withExceptions ( function ( Exceptions $exceptions ) : void {
$exceptions -> report ( function ( InvalidOrderException $e ) {
// ...
}) -> stop ();
})
Helper report()
Para reportar sin mostrar una página de error, usa el helper report().
public function isValid ( string $value ) : bool
{
try {
// Lógica de validación...
} catch ( Throwable $e ) {
report ( $e );
return false ;
}
}
El helper report() te permite dejar constancia del error sin interrumpir la respuesta al usuario. Útil para excepciones no críticas o de jobs en segundo plano.
Evitar reportes duplicados
Si la misma instancia de excepción se pasa varias veces a report(), pueden generarse entradas duplicadas.
Con dontReportDuplicates(), la misma instancia se registra solo la primera vez.
-> withExceptions ( function ( Exceptions $exceptions ) : void {
$exceptions -> dontReportDuplicates ();
})
$original = new RuntimeException ( 'Whoops!' );
report ( $original ); // Se registra
try {
throw $original ;
} catch ( Throwable $caught ) {
report ( $caught ); // Se ignora (misma instancia)
}
Contexto global del log
Para añadir información común a todos los logs de excepción, usa context.
Si está disponible, el ID del usuario actual se añade automáticamente.
-> withExceptions ( function ( Exceptions $exceptions ) : void {
$exceptions -> context ( fn () => [
'app_version' => config ( 'app.version' ),
]);
})
Método context() en la clase de excepción
Definir un método context() en la propia clase de excepción permite añadir información específica de esa excepción al log.
<? php
namespace App\Exceptions ;
use Exception ;
class InvalidOrderException extends Exception
{
public function __construct (
private readonly int $orderId ,
string $message = '' ,
) {
parent :: __construct ( $message );
}
/**
* Devuelve el contexto de la excepción
*
* @return array < string , mixed>
*/
public function context () : array
{
return [ 'order_id' => $this -> orderId ];
}
}
Cambiar el nivel de log
Para registrar ciertas excepciones con un nivel específico, usa level.
use PDOException ;
use Psr\Log\ LogLevel ;
-> withExceptions ( function ( Exceptions $exceptions ) : void {
$exceptions -> level ( PDOException :: class , LogLevel :: CRITICAL );
})
Limitación de reportes
Si se generan muchas excepciones, throttle te permite controlar cuántas se reportan.
use Illuminate\Support\ Lottery ;
use Throwable ;
-> withExceptions ( function ( Exceptions $exceptions ) : void {
// Reporta aleatoriamente 1 de cada 1000
$exceptions -> throttle ( function ( Throwable $e ) {
return Lottery :: odds ( 1 , 1000 );
});
})
Para limitar por minuto, usa Limit.
use Illuminate\Broadcasting\ BroadcastException ;
use Illuminate\Cache\RateLimiting\ Limit ;
use Throwable ;
-> withExceptions ( function ( Exceptions $exceptions ) : void {
$exceptions -> throttle ( function ( Throwable $e ) {
if ( $e instanceof BroadcastException ) {
return Limit :: perMinute ( 300 );
}
});
})
Renderizado de excepciones
Renderizar consiste en convertir la excepción en una respuesta HTTP.
Por defecto Laravel genera la respuesta adecuada; también puedes personalizar el resultado.
Callback personalizado de renderizado
Pasa una closure a render para convertir la excepción en una respuesta.
use App\Exceptions\ InvalidOrderException ;
use Illuminate\Http\ Request ;
-> withExceptions ( function ( Exceptions $exceptions ) : void {
$exceptions -> render ( function ( InvalidOrderException $e , Request $request ) {
return response () -> view ( 'errors.invalid-order' , status : 500 );
});
})
Puedes sobrescribir el renderizado de excepciones integradas (NotFoundHttpException, etc.).
Si la closure no devuelve un valor, se usa el renderizado por defecto.
use Illuminate\Http\ Request ;
use Symfony\Component\HttpKernel\Exception\ NotFoundHttpException ;
-> withExceptions ( function ( Exceptions $exceptions ) : void {
$exceptions -> render ( function ( NotFoundHttpException $e , Request $request ) {
if ( $request -> is ( 'api/*' )) {
return response () -> json ([
'message' => 'Record not found.' ,
], 404 );
}
// Devolver null muestra la página 404 por defecto
});
})
Selección automática JSON / HTML
Laravel decide si devolver HTML o JSON según la cabecera Accept.
Para personalizarlo, usa shouldRenderJsonWhen.
use Illuminate\Http\ Request ;
use Throwable ;
-> withExceptions ( function ( Exceptions $exceptions ) : void {
$exceptions -> shouldRenderJsonWhen ( function ( Request $request , Throwable $e ) {
if ( $request -> is ( 'admin/*' )) {
return true ; // El panel de administración siempre devuelve JSON
}
return $request -> expectsJson ();
});
})
Personalizar toda la respuesta
Con respond puedes procesar la respuesta generada.
use Symfony\Component\HttpFoundation\ Response ;
-> withExceptions ( function ( Exceptions $exceptions ) : void {
$exceptions -> respond ( function ( Response $response ) {
if ( $response -> getStatusCode () === 419 ) {
return back () -> with ([
'message' => 'La página ha caducado. Inténtalo de nuevo.' ,
]);
}
return $response ;
});
})
Clases de excepción personalizadas
Puedes crear tus propias excepciones en app/Exceptions/.
Si defines report() y render(), se invocan automáticamente sin necesidad de configurarlas en bootstrap/app.php.
Crear la clase
Crear la excepción
php artisan make:exception InvalidOrderException
Implementar report() y render()
<? php
namespace App\Exceptions ;
use Exception ;
use Illuminate\Http\ Request ;
use Illuminate\Http\ Response ;
class InvalidOrderException extends Exception
{
public function __construct (
private readonly int $orderId ,
string $message = 'Invalid order.' ,
) {
parent :: __construct ( $message );
}
/**
* Reporta la excepción
*/
public function report () : void
{
// Notificar a un servicio externo, etc.
}
/**
* Convierte la excepción en respuesta HTTP
*/
public function render ( Request $request ) : Response
{
return response () -> view ( 'errors.invalid-order' , [
'orderId' => $this -> orderId ,
], 422 );
}
}
report() admite type-hints para inyección de dependencias. El contenedor de servicios de Laravel las resuelve automáticamente.
Interfaz ShouldntReport
Implementa ShouldntReport en excepciones que no deban reportarse.
Las excepciones con esta interfaz no se reportan nunca.
<? php
namespace App\Exceptions ;
use Exception ;
use Illuminate\Contracts\Debug\ ShouldntReport ;
class PodcastProcessingException extends Exception implements ShouldntReport
{
//
}
Lanzamiento de excepciones
Helper abort()
Puedes generar una respuesta HTTP de error desde cualquier punto de la aplicación.
// 404 Not Found
abort ( 404 );
// Con mensaje
abort ( 403 , 'No tienes permiso para realizar esta operación.' );
abort_if() / abort_unless()
Helpers para lanzar excepciones condicionalmente.
// Aborta si $condition es true
abort_if ( ! $user -> isAdmin (), 403 );
// Aborta si $condition es false
abort_unless ( $user -> hasPermission ( 'edit' ), 403 , 'Permission denied.' );
Muy útiles al comprobar permisos en controladores o middleware. Se combinan a menudo con gates y policies.
Control global de excepciones
Ignorar ciertas excepciones
Indica las excepciones a no reportar con dontReport. La lógica de renderizado personalizado sigue funcionando.
use App\Exceptions\ InvalidOrderException ;
-> withExceptions ( function ( Exceptions $exceptions ) : void {
$exceptions -> dontReport ([
InvalidOrderException :: class ,
]);
})
Para ignorar según condición, pasa una closure a dontReportWhen.
use Throwable ;
-> withExceptions ( function ( Exceptions $exceptions ) : void {
$exceptions -> dontReportWhen ( function ( Throwable $e ) {
return $e instanceof PodcastProcessingException &&
$e -> reason () === 'Subscription expired' ;
});
})
Por defecto, Laravel ignora automáticamente ciertas excepciones: errores 404, token CSRF inválido (419), origen no coincidente (403), etc.
Reactivar excepciones ignoradas por Laravel
Para volver a reportar excepciones ignoradas por defecto, usa stopIgnoring.
use Symfony\Component\HttpKernel\Exception\ HttpException ;
-> withExceptions ( function ( Exceptions $exceptions ) : void {
$exceptions -> stopIgnoring ( HttpException :: class );
})
Páginas de error HTTP
En Laravel puedes definir vistas de error personalizadas por código HTTP.
Crear vistas de error personalizadas
Crea plantillas Blade con el código de estado como nombre de archivo en resources/views/errors/.
resources/
└── views/
└── errors/
├── 404.blade.php
├── 403.blade.php
└── 500.blade.php
En la vista puedes usar la variable $exception para acceder a la información del error.
{{-- resources/views/errors/404.blade.php --}}
<! DOCTYPE html >
< html lang = "es" >
< head >
< meta charset = "UTF-8" >
< title > Página no encontrada </ title >
</ head >
< body >
< h1 > 404 - Página no encontrada </ h1 >
< p > {{ $exception -> getMessage () }} </ p >
< a href = " {{ url ('/') }} " > Volver al inicio </ a >
</ body >
</ html >
Publicar las plantillas de error por defecto
Para partir de las plantillas estándar de Laravel, publícalas con vendor:publish.
php artisan vendor:publish --tag=laravel-errors
Página de error de reserva
Como reserva cuando no hay una vista para un código concreto, puedes crear 4xx.blade.php y 5xx.blade.php.
resources/
└── views/
└── errors/
├── 4xx.blade.php # Fallback para códigos 4xx
└── 5xx.blade.php # Fallback para códigos 5xx
Para 404, 500 y 503, Laravel ya trae páginas por defecto. Para personalizarlas, crea archivos individuales (404.blade.php, etc.), no la de reserva.
Ejemplo práctico: handler de excepciones para API
En aplicaciones API, las excepciones deben devolverse siempre en JSON.
Ejemplo de gestión centralizada de errores de API en bootstrap/app.php:
use Illuminate\Auth\ AuthenticationException ;
use Illuminate\Http\ Request ;
use Illuminate\Validation\ ValidationException ;
use Symfony\Component\HttpKernel\Exception\ NotFoundHttpException ;
-> withExceptions ( function ( Exceptions $exceptions ) : void {
// Devuelve JSON para las solicitudes API
$exceptions -> render ( function ( NotFoundHttpException $e , Request $request ) {
if ( $request -> is ( 'api/*' )) {
return response () -> json ([
'message' => 'Recurso no encontrado.' ,
], 404 );
}
});
$exceptions -> render ( function ( AuthenticationException $e , Request $request ) {
if ( $request -> is ( 'api/*' )) {
return response () -> json ([
'message' => 'Se requiere autenticación.' ,
], 401 );
}
});
$exceptions -> render ( function ( ValidationException $e , Request $request ) {
if ( $request -> is ( 'api/*' )) {
return response () -> json ([
'message' => 'Error de validación.' ,
'errors' => $e -> errors (),
], 422 );
}
});
})
Clase base para excepciones de API
Con una clase base para la API, puedes devolver respuestas uniformes en todos los endpoints.
<? php
namespace App\Exceptions ;
use Exception ;
use Illuminate\Http\ JsonResponse ;
use Illuminate\Http\ Request ;
class ApiException extends Exception
{
public function __construct (
string $message = 'An error occurred.' ,
private readonly int $statusCode = 500 ,
private readonly array $errors = [],
) {
parent :: __construct ( $message );
}
public function render ( Request $request ) : JsonResponse
{
$data = [ 'message' => $this -> getMessage ()];
if ( ! empty ( $this -> errors )) {
$data [ 'errors' ] = $this -> errors ;
}
return response () -> json ( $data , $this -> statusCode );
}
}
Uso en el controlador:
<? php
namespace App\Http\Controllers\Api ;
use App\Exceptions\ ApiException ;
use App\Models\ Order ;
class OrderController extends Controller
{
public function show ( int $id ) : JsonResponse
{
$order = Order :: find ( $id );
if ( ! $order ) {
throw new ApiException ( 'Pedido no encontrado.' , 404 );
}
if ( $order -> isCancelled ()) {
throw new ApiException ( 'Este pedido está cancelado.' , 422 );
}
return response () -> json ( $order );
}
}
Resumen
Resumen del reporte de excepciones
Método Uso $exceptions->report()Registra lógica de reporte personalizada por tipo $exceptions->context()Añade contexto común a todos los logs de excepción Método context() Aporta contexto propio en la clase de excepción Helper report() Reporta sin interrumpir la respuesta dontReportDuplicates()Evita duplicados por la misma instancia Interfaz ShouldntReport Crea excepciones que nunca se reportan
Resumen del renderizado de excepciones
Método Uso $exceptions->render()Devuelve una respuesta personalizada por tipo Método render() Aporta la lógica de renderizado en la propia excepción shouldRenderJsonWhen()Personaliza la detección JSON/HTML respond()Procesa la respuesta generada
Resumen de las páginas de error HTTP
Basta con crear archivos como resources/views/errors/404.blade.php para que se usen automáticamente
Se accede al detalle del error con la variable $exception
php artisan vendor:publish --tag=laravel-errors publica las plantillas por defecto
Con 4xx.blade.php / 5xx.blade.php defines páginas de reserva
Buenas prácticas en producción
Configura siempre APP_DEBUG=false para no exponer la traza al usuario
Integra servicios externos de seguimiento de errores como Sentry o Flare para centralizar
Usa throttle() para evitar la saturación del log ante grandes volúmenes de excepciones
En los endpoints de API, mantén un formato JSON de error consistente