Phoenix - returning custom 500 (Internal Server Error). How to?

I need to return a custom error message (json) for Internal Server Error.
The message is needed for explaining some error causes such as: corrupt file, internal path not found, bad json, python script execution error etc.
The 500 error would be raised in certain places in my Phoenix web API code when such upstream errors/exceptions occurs.

In the official doc is not clear how exactly to do this.

Thanks for help!

conn
|> put_status(500)
|> json(%{error: "Custom error message"})

Will this work for you?

Thanks but not really… different context, cannot use conn there
I need something to “raise”:
raise MyApp.MyCustomError {<code>, <msg>}
more exactly:
raise MyApp.MyCustomError {:500, "Script xxx.py did not execute successfully"}
I think you got the idea.

have you tried to just return a map? I got the idea from here

Review the Custom Exceptions section of that same guide.

You will need a defimpl Plug.Exception if you want your exception to be rendered as an HTTP response through Plug.

If you want to render your custom error in production, you will also need to add a pattern match to the render/2 function on your ErrorView. To ensure this is working correctly, you may want to set debug_errors: false on your Endpoint config in
dev.exs. :slight_smile:

Edits: typos

4 Likes

Thanks, I eventually managed to do it with defimpl.
I’m writing here the solution, it might help others with similar issues:
In the myapp_web.ex:

defmodule Myapp.Customerror do
  defexception [:message]
end


defimpl Plug.Exception, for: Myapp.Customerror do
  def status(_exception), do: 500
end

error_view.ex:

  def render("500.json", %{conn: conn}) do
    %{error: conn.assigns.reason.message}
  end

.Finally, in my code:

raise Myapp.Customerror, "My custom message"

Cheers,
T.

4 Likes

I dug into this, and found it can be simplified a bit when defining the exception.

  1. Define the exception:

lib/your_project_web/custom_500_error.ex

defmodule YourProjectWeb.Custom500Error do
  defexception [:message, plug_status: 500]
end

  1. Handle the exception in the view layer:

lib/your_project_web/views/error_view.ex

defmodule YourProjectWeb.ErrorView do
  def render("500.json", %{conn: conn}), do: %{error: conn.assigns.reason.message}
end

NOTE: I haven’t tried this in Phoenix v1.7 and up, but it works in v1.6 with the old style of views. Also note that the *.json template means this is being piped through an :api pipeline in the router. A plain HTML template (rendered through the :browser pipeline in the router) would probably need to match on "500.html" instead of "500.json".


  1. Raise the exception in your code:

lib/your_project_web/controllers/some_controller.ex

defmodule YourProjectWeb.SomeController do
  use YourProjectWeb, :controller

  def some_action(_conn, _params) do
    raise YourProjectWeb.Custom500Error, "Hello world!"
  end
end

Note that when calling exceptions, you can also pass arguments to it as a keyword list. For example, this would work:

raise YourProjectWeb.Custom500Error, message: "Hello world!"

Since we defined the plug_status when we created the exception, you could also override this if you wanted to make a more generic exception handler:

raise YourProjectWeb.CustomResponseError, plug_status: 400, message: "Hello world!"

From what I understand, you would need to make sure you added a clause to error_view.ex for each status code you added. (I found some workarounds when I was looking into this, but they seemed kinda hacky. Left as an exercise to the reader :wink: )