Skip to content

Error Pages

Error pages live in resources/views/errors/. Laravel renders {status}.blade.php for an HTTP error with that status. They use one of two layouts, depending on whether the application can be trusted to work when the error happens.

PageViewLayout
401, 402, 403, 404, 419, 429<x-error-layout title="...">The public layout
503 (maintenance, external service outage)<x-error-layout title="..." :navigation="false"><x-layouts.error>
500Its own body<x-layouts.error>
Database pausedIts own body<x-layouts.error>

<x-error-layout> (App\View\Components\ErrorLayout) renders resources/views/errors/layout.blade.php: a heading from title, the slot as the message, and a Back to Homepage button. By default it renders on the public layout, so the header keeps the Help menu and Sign In or the user menu, and the page uses the app panel’s theme.

The public layout looks up the signed-in user inside rescue(), so a failing lookup leaves the page signed out instead of failing it. A fallback route sends unknown URLs through the web middleware, so the not-found page knows who is signed in.

<x-layouts.error> (resources/views/components/layouts/error.blade.php) renders the pages that can’t rely on the application working: 500, 503 and database-paused. It must work when nothing else does:

  • It loads no Filament, makes no auth calls and doesn’t touch the database.
  • Its styles come from resources/css/errors.css, which imports Tailwind and the Northwestern design tokens directly instead of a panel theme.
  • It renders <x-site-header> without a Help menu or user menu, and the Northwestern footer.

<x-error-layout :navigation="false"> uses this layout. The 503 page passes it, because maintenance mode and outages are exactly when the panel and the database may be unavailable.

  1. Edit the message of an existing client error in its view, for example resources/views/errors/404.blade.php:

    <x-error-layout title="Page Not Found">
    We can't find that page. Check the address and try again.
    </x-error-layout>
  2. Add a page for another status as resources/views/errors/{status}.blade.php. Use <x-error-layout>, and pass :navigation="false" when the error means the application itself may not be working.

  3. Cover a whole range with 4xx.blade.php or 5xx.blade.php. Laravel falls back to them when no view matches the exact status. Without one, a status such as 405 renders Laravel’s plain error page.

  4. Check it in the browser tests. Add the status to the list in the error page test in tests/Browser/Pages/PublicPagesTest.php. ErrorPages::path($status) registers a test route that answers with that status, so pages only a failure shows are checked too. See Browser Tests.

Both resources/css/filament/app/theme.css and resources/css/errors.css compile classes from resources/views/errors/, so new views there need no @source change.

resources/views/errors/500.blade.php renders for unhandled exceptions when APP_DEBUG is off. With debug on, Laravel shows its own exception page instead.

It asks the user to try again and links to the IT Service Desk. The rest depends on the environment and on Sentry:

  • Error ID. When Sentry captured the exception, the page shows Sentry’s event ID, so a user can quote it in a report.
  • Help Us Fix This. With an event ID, the page also shows a form for the user’s name, email and a description. It sends the report to Sentry with captureFeedback, linked to the event. Name and email are filled in for signed-in users. See Sentry.
  • Technical Details. The exception message and stack trace show to everyone outside production, so testers without Sentry access can describe failures. In production they show only to users with the ManageAll permission.

The user and permission lookups are wrapped in rescue(): if the database is down, the page renders without them and hides the technical details in production.

Non-production environments on Aurora Serverless v2 can scale the database to zero. The first request after a pause can time out while the database wakes up. bootstrap/app.php uses chassis’s DatabasePausedDetector to recognize that PostgreSQL connection timeout and renders errors.database-paused with status 500 in place of the 500 page.

The page names the environment (APP_ENV), explains that its database is starting, and reloads itself after 30 seconds. Outside production these timeouts aren’t reported to Sentry.

App\Domains\Core\Exceptions\ServiceDownError is thrown when a service the application depends on is unavailable. The starter throws it when Directory Search can’t provide a user’s details during single sign-on, after retrying.

It renders the 503 page instead of the 500 page, because the fault is in another service. API and JSON requests get a Problem Details 503 response with Retry-After: 60. The error is still reported. To name another service in the message, add a case to App\Domains\Core\Enums\ExternalService.