Feb 13, 2019

Custom error pages in Phoenix

In today’s short post, we’ll be looking at how to set up custom error pages in Phoenix and how to view them when running in development.

A common example of an error is navigating to page that doesn’t exist. For example, with a default Phoenix application, when navigating to a non-existant page, we see the following:

Now this is not what a user would see when our application is running in production. To see the production message we need to update our development configuration file.

Displaying production error pages when running in development

We need to comment out the debug_errors: true statement (or set it to false).

/config/dev.exs
config :my_app, MyAppWeb.Endpoint,
  http: [port: 4000],
  # debug_errors: true,
  code_reloader: true,

Now when we re-start the server and view our page.

Terminal
mix phx.server

We see the production message.

That’s a pretty sparse error page, but isn’t terribly unreasonable, and you could leave it as is. What if you want to provide a custom 404 page however?

Adding custom error pages

Turns out this is super simple to do. All we need to do is create some error templates. So let’s see how we could create a custom 404 page.

Adding a custom 404 page

First we need to create an error folder within the templates directory. Then we need a 404 specific error page.

Terminal
mkdir lib/<your_app_web>/templates/error
touch lib/<your_app_web>/templates/error/404.html.eex

It’s now simply a matter of filling in the contents of the file.

/lib/your_app_web/templates/error/404.html.eex
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>CustomErrorPages · Phoenix Framework</title>
    <link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
  </head>
  <body>
    <header>
      <section class="container">
        <nav role="navigation">
          <ul>
            <li><a href="https://hexdocs.pm/phoenix/overview.html">Get Started</a></li>
          </ul>
        </nav>
        <a href="http://phoenixframework.org/" class="phx-logo">
          <img src="<%= Routes.static_path(@conn, "/images/phoenix.png") %>" alt="Phoenix Framework Logo"/>
        </a>
      </section>
    </header>
    <main role="main" class="container">
      <h1>Sorry, nothing was found here!</h1>
    </main>
    <script type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
  </body>
</html>

You’ll likely want to use the same contents for your error page as the contents of your templates/layout/app.html.eex file… just replacing the <main> section with an error message.

Using the above, our 404 error now looks like:

Adding custom pages for other status codes

The procedure for adding custom pages for other status codes is the same as what we did for 404 errors. The exception to this is if you are using an action_fallback in your controller. Any error status for which you use a fallback action will make use of the default layout file. For instance, we used a fallback action and controller in our post on authorization with Bodyguard, i.e. in our controller we wired up a fallback controller like so:

defmodule WarehouseWeb.ProductController do
  use WarehouseWeb, :controller
  ...
  action_fallback WarehouseWeb.FallbackController
  ...

Our fallback controller renders 403 errors as below:

def call(conn, {:error, :unauthorized}) do
  conn
  |> put_status(:forbidden)
  |> put_view(WarehouseWeb.ErrorView)
  |> render(:"403")
end

As a result our default layout will be used for 403 errors. This means we just need to provide our desired error message in the 403 error file. For example the entire contents of our 403 file could be something along the lines of:

/lib//templates/error/403.html.eex
<h1>Sorry, not authorized to perform that action!</h1>

Simple!

So that’s it for custom error pages in Phoenix, after finishing things up, it’s probably a good idea to revert your development configuration:

/config/dev.exs
config :my_app, MyAppWeb.Endpoint,
  http: [port: 4000],
  debug_errors: true,
  code_reloader: true,

Thanks for reading and I hope you enjoyed the post!



Comment on this post!