May 15. 2018 · by Helge Sverre

How to add a custom 404 page in CraftCMS

Creating a custom 404 page in CraftCMS is pretty easy, just create a 404.twig page in the craft/template folder or in Craft 3 the /templates folder, and that template will be magically loaded whenever a page cannot be found.

Template folder structure of this site
Shell
c:\xampp\htdocs\startingcraft\templates
│   .gitkeep
│   404.twig  <--- Add this file
│   about.twig
│   index.twig
│   _layout.twig
│
├───articles
│   │   _category.twig
│   │   _entry.twig
│
└───partials
        article-list-entry.twig

The way this seemingly magic works, is as follows, when an error occurs the actionRenderError method in the TemplateController is called, below is a snippet of code taken from that file (method starts around line 154~).

/vendor/craftcms/cms/src/controllers/TemplateController.php
PHP
/**
 * Renders an error template.
 *
 * @return Response
 */
public function actionRenderError(): Response
{
    // Some code here ...

    if (Craft::$app->getRequest()->getIsSiteRequest()) {
        $prefix = Craft::$app->getConfig()->getGeneral()->errorTemplatePrefix;
        
        // This is where the magic happens
        if ($this->getView()->doesTemplateExist($prefix . $statusCode)) {
            $template = $prefix . $statusCode;
        } else if ($statusCode == 503 && $this->getView()->doesTemplateExist($prefix . 'offline')) {
            $template = $prefix . 'offline';
        } else if ($this->getView()->doesTemplateExist($prefix . 'error')) {
            $template = $prefix . 'error';
        }
    }
    
    // More code here ...
    // But eventually the selected template is rendered
    return $this->renderTemplate($template, $variables);
}

The magic line: $this->getView()->doesTemplateExist($prefix . $statusCode)

It basically checks if a template with the same filename as the status code exists(404 for not found, 500 for server error etc, complete list here).

It also checks if an "offline" template exists as when CraftCMS is being installed it outputs a 503 response, and I also believe it does that when you're updating it via the updater, don't quote me on that though.

If none of those templates exist, it will looks for a final template called "error", which in case you just want to route every error into a shared "oops we messed up" page, then you can just create an error.twig template and be good to go.

The $prefix variable is a blank string ("") by default, it is a configuration option that's mentioned here which let's you set a custom directory where it will look for all error templates (You might want to put all error templates in /templates/_errors/ or something).

I really suggest looking through the source code of craft to gain a better understanding of what is happening behind the scenes, I'll do more of these kinds of articles in the future where we look at how to do something simple and magical, then going into the code and seeing how it is actually implemented.