1

I want to use different error pages for the admin panel and the frontend in Laravel 12. I created a custom exception class for this purpose, but I haven’t figured out how to activate or use it yet. This is my first time customizing exceptions in Laravel 12.

Could you please help me identify and fix any mistakes or missing parts?

<?php

namespace App\Exceptions;

use Throwable;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Illuminate\Contracts\Debug\ExceptionHandler as ExceptionHandlerContract;

class ErrorViewResolver implements ExceptionHandlerContract
{
    public function report(Throwable $e)
    {
    }

    public function shouldReport(Throwable $e)
    {
        return false;
    }

    public function render($request, Throwable $e): Response|JsonResponse
    {
        $isHttpException = $e instanceof HttpException;
        $statusCode = $isHttpException ? $e->getStatusCode() : 500;

        if ($request->is('admin/*')) {
            if (view()->exists("admin.errors.{$statusCode}")) {
                return response()->view("admin.errors.{$statusCode}", [
                    'exception' => $e
                ], $statusCode);
            }
        }
        else {
            if (view()->exists("frontend.errors.{$statusCode}")) {
                return response()->view("frontend.errors.{$statusCode}", [
                    'exception' => $e
                ], $statusCode);
            }
        }

        return app(\Illuminate\Foundation\Exceptions\Handler::class)->render($request, $e);
    }

    public function renderForConsole($output, Throwable $e)
    {
        (new \Illuminate\Foundation\Exceptions\Handler(app()))->renderForConsole($output, $e);
    }
}

2 Answers 2

3

I don't think you need to make a full implementation of the ExceptionHandler contract to achieve your logic. In laravel 11, you'd only need to register a renderable callback inside the Handler (located in app/Exceptions/Handler.php)

<?php
// ./app/Exceptions/Handler.php

namespace App\Exceptions;

use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Throwable;

class Handler extends ExceptionHandler
{
    ...

    public function register(): void
    {
        $this->renderable(function (HttpException $ex) {
            $statusCode = $ex->getStatusCode();
            $view = $request->is('admin/*')
                ? "admin.errors.{$statusCode}"
                : "frontend.errors.{$statusCode}";

            if (view()->exists($view)) {
                return response()->view($view, ['exception' => $ex], $statusCode);
            }
        });

        $this->renderable(function (Throwable $th) {
            $view = $request->is('admin/*')
                ? 'admin.errors.500'
                : 'frontend.errors.500';

            if (view()->exists($view)) {
                return response()->view($view, ['exception' => $th], 500);
            }
        });
    }
}

In laravel 12, that logic has been moved to the bootstrap/app.php file, but the syntax should largely remain the same.

<?php
// ./bootstrap/app.php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Throwable;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        //
    })
    ->withExceptions(function (Exceptions $exceptions) {
        // or $exceptions->renderable if you prefer to keep the syntax as close to 11 as possible. The render and renderable methods do the exact same thing.
        $exceptions->render(function (HttpException $ex) {
            $statusCode = $ex->getStatusCode();
            $view = $request->is('admin/*')
                ? "admin.errors.{$statusCode}"
                : "frontend.errors.{$statusCode}";

            if (view()->exists($view)) {
                return response()->view($view, ['exception' => $ex], $statusCode);
            }
        });

        $exceptions->render(function (Throwable $th) {
            $view = $request->is('admin/*')
                ? 'admin.errors.500'
                : 'frontend.errors.500';

            if (view()->exists($view)) {
                return response()->view($view, ['exception' => $th], 500);
            }
        });
    })->create();

I'll just add one more thing. Just like registering routes, the order in which you register these callbacks matters. The ExceptionHandler just loops through its callbacks and returns the first usable (not null) response.


If you feel this is too bloated or cluttered because you have that much exception handling logic, you can extract the closures into classes.

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        //
    })
    ->withExceptions(function (Exceptions $exceptions) {
        $exceptions->render((new RenderHttpExceptions)(...)); // $exceptions->handler->renderable(new RenderHttpException); should also work, because the handler converts non callables to callables.

        $exceptions->render((new RenderOtherThrowables)(...));
    })->create();
class RenderHttpExceptions
{
    public function __invoke(HttpException $ex)
    {
        ...
    }
}
class RenderOtherThrowables
{
    public function __invoke(Throwable $th)
    {
        ...
    }
}

Or just move the whole of callback registering to another class

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        //
    })
    ->withExceptions(function (Exceptions $exceptions) {
        ExceptionHandling::registerCallbacks($exceptions);
    })->create();
class ExceptionHandling
{
    public static function registerCallbacks(Exceptions $exceptions): void
    {
        $exceptions->render(function (HttpException $ex) { ... });

        $exceptions->render(function (Throwable $th) { ... });
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

However, if I write it inside app.php like this, the code inside app.php will become cluttered. Because of this, I want to write it in a separate file.
I've edited the answer with a couple other options. If you still want to use your very own exception handler you might want to register your class as the singleton for \Illuminate\Contracts\Debug\ExceptionHandler::class,.
0
use Symfony\Component\HttpFoundation\Response;
->withExceptions(function ($exceptions) {
    $exceptions->respond( function ( Response $response ) {
            if ( $response->getStatusCode() === 429 ) {
                return response()->json( [
                    'status'  => 'error',
                    'message' => 'Too many requests, please try again later.',
                ] );
            }
            return $response;
        } );
});

You can use it in bootstrap/app.php

Try it i hope it will show custom message

1 Comment

As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.