.cursor/skills/laravel-best-practices/rules/error-handling.md
There are two valid approaches — choose one and apply it consistently across the project.
Co-location on the exception class — keeps behavior alongside the exception definition, easier to find:
class InvalidOrderException extends Exception
{
public function report(): void { /* custom reporting */ }
public function render(Request $request): Response
{
return response()->view('errors.invalid-order', status: 422);
}
}
Centralized in bootstrap/app.php — all exception handling in one place, easier to see the full picture:
->withExceptions(function (Exceptions $exceptions) {
$exceptions->report(function (InvalidOrderException $e) { /* ... */ });
$exceptions->render(function (InvalidOrderException $e, Request $request) {
return response()->view('errors.invalid-order', status: 422);
});
})
Check the existing codebase and follow whichever pattern is already established.
ShouldntReport for Exceptions That Should Never LogMore discoverable than listing classes in dontReport().
class PodcastProcessingException extends Exception implements ShouldntReport {}
A single failing integration can flood error tracking. Use throttle() to rate-limit per exception type.
dontReportDuplicates()Prevents the same exception instance from being logged multiple times when report($e) is called in multiple catch blocks.
Laravel auto-detects Accept: application/json but API clients may not set it. Explicitly declare JSON rendering for API routes.
$exceptions->shouldRenderJsonWhen(function (Request $request, Throwable $e) {
return $request->is('api/*') || $request->expectsJson();
});
Attach structured data to exceptions at the source via a context() method — Laravel includes it automatically in the log entry.
class InvalidOrderException extends Exception
{
public function context(): array
{
return ['order_id' => $this->orderId];
}
}