Exception Handling Configuration
Configure custom error rendering and reporting.
Exception handling configuration lives in config/exceptions.php. Customize how exceptions are displayed to users and how they're logged.
Full Configuration Example #
return [
// Custom renderers: exception class => renderer callable
'renderers' => [
\App\Exceptions\MaintenanceException::class => function (Throwable $e, Request $request) {
return Response::html(
view('errors/maintenance', ['message' => $e->getMessage()]),
503
);
},
\VelvetCMS\Exceptions\ValidationException::class => function (Throwable $e, Request $request) {
if ($request->wantsJson()) {
return Response::json(['errors' => $e->errors()], 422);
}
return Response::redirect()->back()->withErrors($e->errors());
},
],
// Custom reporters: exception class => reporter callable
'reporters' => [
\PDOException::class => function (Throwable $e, Request $request, $logger) {
$logger->critical('Database error', [
'message' => $e->getMessage(),
'query' => $e->queryString ?? null,
'url' => $request->uri(),
]);
},
\RuntimeException::class => function (Throwable $e, Request $request, $logger) {
// Send to external error tracking
Sentry::captureException($e);
},
],
];
Renderers #
Renderers convert exceptions into HTTP responses. The exception handler checks for a matching renderer and uses it instead of the default error page.
Signature:
function(Throwable $e, Request $request): Response
Matching Rules #
- Exact class matches are checked first
- Parent classes are not matched (be specific)
- First matching renderer wins
- If no renderer matches, the default handler runs
Common Patterns #
Custom 404 page:
\VelvetCMS\Exceptions\NotFoundException::class => function ($e, $request) {
return Response::html(view('errors/404'), 404);
},
API error responses:
\App\Exceptions\ApiException::class => function ($e, $request) {
return Response::json([
'error' => $e->getMessage(),
'code' => $e->getCode(),
], $e->getStatusCode());
},
Maintenance mode:
\App\Exceptions\MaintenanceException::class => function ($e, $request) {
return Response::html(view('errors/maintenance'), 503)
->header('Retry-After', 3600);
},
Reporters #
Reporters handle logging and external error tracking. They run before rendering, so you can log errors even if rendering fails.
Signature:
function(Throwable $e, Request $request, LoggerInterface $logger): void
Common Patterns #
External error tracking:
\Throwable::class => function ($e, $request, $logger) {
Bugsnag::notifyException($e);
},
Slack notifications for critical errors:
\App\Exceptions\CriticalException::class => function ($e, $request, $logger) {
$logger->critical($e->getMessage());
SlackNotifier::alert("Critical error: {$e->getMessage()}");
},
Skip logging for specific exceptions:
\App\Exceptions\ExpectedValidationError::class => function ($e, $request, $logger) {
// Don't log validation errors-they're expected
},
Default Behavior #
Without custom configuration:
- Production (
debug = false): Shows a generic error page - Development (
debug = true): Shows detailed stack trace - Logging: All exceptions logged to
storage/logs/
Tips #
- Keep renderers fast-they run on every error
- Use reporters for external services, not renderers
- Test your custom handlers with each exception type
- Consider user experience: helpful messages without exposing internals