In Phoenix, controller actions usually render templates wrapped in a layout β€” like app.html.heex. But sometimes, especially for API endpoints or standalone HTML fragments, you want no layout at all.

Here’s how to render a controller action without any layout, including the root layout introduced in Phoenix 1.6+.

The problem

You define a route like this:

scope "/api", MyAppWeb do
  pipe_through :browser
  get "/my-endpoint", ApiController, :my_endpoint
end

And in your controller:

def my_endpoint(conn, _params) do
  conn
  |> put_layout(false)
  |> render("my_endpoint.html")
end

But the template still renders inside the root layout (e.g., app.html.heex). Why?

The fix

Phoenix now separates the root layout from the view layout. To disable both, do this:

def my_endpoint(conn, _params) do
  conn
  |> put_root_layout(false)  # disables the outer layout (Phoenix 1.6+)
  |> put_layout(false)       # disables the controller/view layout
  |> render("my_endpoint.html")
end

Now your template renders as-is, without any wrapping HTML.

Bonus tips

  • Returning JSON or plain text? Just use json/2 or text/2 β€” no layout applies:

    def dkk_calendar(conn, _params) do
      json(conn, %{events: []})
    end
    
  • Serving an .ics file or static content? Use put_resp_content_type/2 and send_resp/3 directly.

  • Prefer pipe_through :api for endpoints like /api/dkk-calendar, unless you need session or CSRF protection.