Custom Error Pages in Laravel
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 commandphp 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!
Subscribe to my newsletter
Read articles from Ahmed Abdelaal directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by