Custom Error Pages in Laravel

Ahmed AbdelaalAhmed Abdelaal
5 min read

Laravel makes it easy to display custom error pages for various HTTP status codes. For example, to customize the error page for 404 HTTP status codes, create a resources/views/errors/404.blade.php view template. This view will be rendered for all 404 errors generated by your application. The views within this directory should be named to match the HTTP status code they correspond to. The Symfony\Component\HttpKernel\Exception\HttpException instance raised by the abort function will be passed to the view as an $exception variable:

<h2>{{ $exception->getMessage() }}</h2>

You may publish Laravel's default error page templates using the vendor:publish Artisan command. Once the templates have been published, you may customize them to your liking:

php artisan vendor:publish --tag=laravel-errors

You can also customize the error view pages path

Here are two ways of customizing:

1.) in your config/view.php you have an array paths - if you add a new folder here, then Laravel will search all paths (with /errors at the end) until it finds the correct error-page.

if you don't find the config/view.php you can publish it using this command
php artisan config:publish view the following is the default configuration

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | View Storage Paths
    |--------------------------------------------------------------------------
    |
    | Most templating systems load templates from disk. Here you may specify
    | an array of paths that should be checked for your views. Of course
    | the usual Laravel view path has already been registered for you.
    |
    */

    'paths' => [
        resource_path('views'),
    ],

    /*
    |--------------------------------------------------------------------------
    | Compiled View Path
    |--------------------------------------------------------------------------
    |
    | This option determines where all the compiled Blade templates will be
    | stored for your application. Typically, this is within the storage
    | directory. However, as usual, you are free to change this value.
    |
    */

    'compiled' => env(
        'VIEW_COMPILED_PATH',
        realpath(storage_path('framework/views'))
    ),

];
<?php 
// you can add a new path which will contain http errors view 
// but make sure views path itself dont have a errors folder 
// because laravel will match the first 
'paths' => [
        resource_path('views'),
        resource_path('views/foo/bar'),
    ],
// if you want to make it you can also rearange the order 
'paths' => [
        resource_path('views/foo/bar')
        resource_path('views'),
    ],

Laravel will search all paths with '/errors' at the end until it finds the correct error page. Where does this search happen? Let's take a closer look.

In Illuminate\Foundation\Exceptions\Handler in renderHttpException, Laravel registers error paths by calling the method registerErrorViewPaths.

<?php 
/**
 * Render the given HttpException.
 *
 * @param \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface $e
 * @return \Symfony\Component\HttpFoundation\Response
 */
protected function renderHttpException(HttpExceptionInterface $e)
{
    $this->registerErrorViewPaths();
    ...
}

this registerErrorViewPaths method run the invokable class RegisterErrorViewPaths

<?php 
/**
 * Register the error template hint paths.
 *
 * @return void
 */
protected function registerErrorViewPaths()
{
    (new RegisterErrorViewPaths)();
}
<?php

namespace Illuminate\Foundation\Exceptions;

use Illuminate\Support\Facades\View;

class RegisterErrorViewPaths
{
    /**
     * Register the error view paths.
     *
     * @return void
     */
    public function __invoke()
    {
        View::replaceNamespace('errors', collect(config('view.paths'))->map(function ($path) {
            return "{$path}/errors";
        })->push(__DIR__.'/views')->all());

    }
}
// in Illuminate\View\Factory;
/**
 * Replace the namespace hints for the given namespace.
 *
 * @param string $namespace
 * @param string|array $hints
 * @return void
 */
public function replaceNamespace($namespace, $hints)
{
   // $namespace ='errors'
    $this->hints[$namespace] = (array)$hints;
}

// the following without basepath only for explinations 
$hits = [
  resources/views/errors,
  resources/views/foo/bar/errors,
  vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views
]

After the View Path has been registered, Laravel tries to find the correct view page for an HTTP exception.

<?php 

$view = $this->getHttpExceptionView($e)

    protected function getHttpExceptionView(HttpExceptionInterface $e)
{
    // $view = 'errors::404'
    // view exists will check if 404.blade.php exists on errors paths (all errors paths first one match)
    // if exists will return view
    // if not will try to guess fallback blade for exmaple 
    // errors::404.blade.php => errors::4xx.blade.php
    // if exists will return this blade other wise will return null 
    $view = 'errors::' . $e->getStatusCode();

    if (view()->exists($view)) {
        return $view;
    }

    $view = substr($view, 0, -2) . 'xx';

    if (view()->exists($view)) {
        return $view;
    }

    return null;
}

if you want to make two blade files to handle all http exceptions inside resources/views/errors

4xx.blade.php and 5.xx.blade.php

you will surprise that laravel is still render 404.blade.php from the vendor path instead of displaying 4xx.blade.php from resource path

due to the mechanism of finding files from hits, Laravel will search for the blade in all hits paths until find the correct file .

if Framework find it in the first folder will return it from the first folder if not will continue searching .

<?php 

// do you still remember hits ? 
// if you want to display the errors from foo/bar folder
// make sure you have completly remove error path from views 
// becuase if laravel find the file in the first folder laravel will return it  
$hits = [
  resources/views/errors,
  resources/views/foo/bar/errors,
  vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views
]


$view = 'errors::404'
view()->exists($view)

/**
 * Find the given view in the list of paths.
 *
 * @param string $name
 * @param array $paths
 * @return string
 *
 * @throws \InvalidArgumentException
 */
    protected function findInPaths($name, $paths)
{
    foreach ((array)$paths as $path) {
        foreach ($this->getPossibleViewFiles($name) as $file) {
            if ($this->files->exists($viewPath = $path . '/' . $file)) {
                return $viewPath;
            }
        }
    }

    throw new InvalidArgumentException("View [{$name}] not found.");
}

so if you want to follow the above way we need to remove vendor path from hits, let's see it in the second way

2.) you can override the registerErrorViewPaths method Handler.php

if you are in laravel11 we need to overwrite the handler.php it's self, else you can overwrite registerErrorViewPaths in app/Exception/Handler.php

How we can overwrite the handler.php it's self in laravel 11 you can follow this way

https://ahmedabdelaal.hashnode.dev/error-handling-in-laravel-11

<?php  
use Illuminate\Support\Facades\View;

/**
 * Register the error template hint paths.
 */
protected function registerErrorViewPaths(): void
{
    $errorPath = resource_path('views/foo/errors')
    View::replaceNamespace('errors',[$errorPath]);
}

then laravel will look only on this folder views/foo/errors.

if you have many domains for example and you want to customize views based on domains

<?php  
use Illuminate\Support\Facades\View;

/**
 * Register the error template hint paths.
 */
protected function registerErrorViewPaths(): void
{
    $subDomain = collect(explode('.', $hostname = request()->getHost()))->first();

     $domain = match ($subDomain) {
           'admin' => 'admin',
            'seller' => 'seller',
            default => 'default'
        };

    $errorPath = "views/{$domain}/errors"
    View::replaceNamespace('errors',[$errorPath]);
}

Conclusion

That’s a brief explanation of customizing view error paths in Laravel. For more details, you can check the official Laravel documentation here.

I hope you found this article helpful. If you did, please consider liking it. Thank you!

10
Subscribe to my newsletter

Read articles from Ahmed Abdelaal directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ahmed Abdelaal
Ahmed Abdelaal