When building Rails apps, we often take render
for granted. It’s easy to let Rails implicitly render the default template or to drop in render :new
to render a different template.
However, as you get deeper into Rails, you learn that there’s a lot more under the hood: whether you want to render JSON APIs, partials, custom inline ERB, or raw files, the render
method is one of the most flexible and powerful tools in the Rails controller toolbox.
Let’s explore the various ways to use render
in controller actions, starting from the basics, and then venturing into the lesser-known territory. Knowing all the ways render
works helps you build better controllers, whether you’re building HTML pages, APIs, or admin panels. Once you’ve mastered the render API, you can keep your controllers focused, your templates clean, and your responses accurate.
This post focuses on the external API of the render
method. If you're curious about how it works behind the scenes by reading the Rails source code, check out the following post.

Default Behavior: Rendering the Action's View
In the controller actions, if you don't explicitly render, Rails will render the template with the name matching the name of the controller action. This is the most common case, and Rails handles it implicitly.
For example, if you have this controller:
class PostsController < ApplicationController
def index
@posts = Post.all
end
end
Rails will render the app/views/posts/index.html.erb
view template, without you having to explicitly render anything. In fact, the index
action is not even needed, and Rails will assume it by default.
This is one of the best examples of convention over configuration, a core principle of the Rails framework. This is the sort of magic people talk about when they say Rails is magic.
If you do want to be explicit, pass the name of the action as a symbol:
render :index
A common use case is when the validation fails during a create action, and we need to re-render the form using the new action.
def create
@post = Post.new(post_params)
if @post.save
redirect_to @post, notice: "Post was successfully created."
else
render :new, status: :unprocessable_entity
end
end
Remember that we're not redirecting to the new
action above. The code under the new
method won't be executed. We're simply rendering the new
template.
You can also render another format like .json.erb
:
render :index, formats: :json
Note: Calling the render method doesn't magically halt execution of your controller's action method. Rails will throw the DoubleRenderError
if render in the middle of the action without returning explicitly.
def show
@book = Book.find(params[:id])
if @book.special?
render action: "special_show"
end
render action: "regular_show"
end
If you don't want to execute the rest of the code that follows, return early.
def show
@book = Book.find(params[:id])
if @book.special?
render action: "special_show"
return
end
render action: "regular_show"
end
If you want to see the exact output of the render
method, call the render_to_string
method. It uses the same options as the render
method but returns the string result without sending response back to the server.
Render a Template from Another Controller
To render a view template from another controller, you can specify the full path to the template using the render
method. For example:
# app/controllers/orders_controller.rb
class OrdersController
def show_summary
@order = Order.find(params[:id])
render template: "invoices/summary"
end
end
This will render the app/views/invoices/summary.html.erb
template, even though the action is in OrdersController
. The path is relative to app/views
, without the file extension.
If you want, you can also specify a full path to a template in a different controller:
render "admin/dashboard/show"
Rails will look for app/views/admin/dashboard/show.html.erb
.
Rendering Text, HTML, JSON, and XML
When building APIs or responding with simple messages, you often need to render raw content. You can do this using the plain
, html
, json
, or xml
options to the render
method.
Text
render plain: "Not authorized", status: :unauthorized
When using the :plain
option, the text is rendered without the layout. If you want to use the layout, add the layout: true
option and create the layout file .text.erb
extension.
HTML
render html: "<strong>Saved!</strong>".html_safe
JSON
render json: @user
Note, the argument (@user above) must respond to to_json
. The render
method automatically calls to_json
for you.
Or, customize the output:
render json: { id: @user.id, name: @user.name }
If you want to explore the Rails internals to understand how it really works, check out this post.

XML
render xml: @user
Same with JSON, the render
method will call to_xml
for you.
Rendering Raw File or Binary Data
If you want to send a file to the client (such as a static page), Rails can render a raw file from an absolute path.
render file: Rails.root.join('public/terms.html')
Make sure you don't use user-provided inputs otherwise it can be used to access sensitive files in your app.
By default, this renders as HTML. If you want to serve it as text:
render file: Rails.root.join('public/data.csv'), content_type: 'text/csv'
Binary content
send_data image_data, type: 'image/png', disposition: 'inline'
Or for file downloads:
send_file '/path/to/file.pdf', type: 'application/pdf', disposition: 'attachment'
Technically not render
, but still part of controller response APIs. If you don't need to provide the layout, send_file
is often a faster and better option.
Rendering Nothing
Sometimes, you don't want to render any body, but just want to send a header. In this case, you can use the head
method, passing the HTTP status code.
head :ok
# OR
head :unauthorized
Options Available to Render
Here’s a summary of the common options you can pass to render
:
Rendering mode
:partial
– Renders a partial.:inline
– Renders an inline string of ERB.:file
– Renders a file from disk.:body
– Returns a raw string as the response body.:plain
– Renders plain text.:html
– Renders raw HTML (you must mark ithtml_safe
).:json
– Renders JSON (callsto_json
on object).:xml
– Renders XML (callsto_xml
).:js
– Renders JavaScript.
Options
:content_type
– Overrides the MIME type.:assigns
– Provides the hash of instance variable assignments for the template.:locals
– Provides the hash of local variable assignments for the template.:layout
- Specify a different layout or render without a layout withlayout: false
.:location
– Sets theLocation
header. Often used withstatus: :found
.:status
– Sets the HTTP status code (e.g.:ok
,:not_found
).:template
– Renders a specific template file.:action
– Renders a different action’s view in the same controller.:formats
– Change the default format specified in the request (:html
by default):variants
– Look for template variations of the same format.
Before rendering, Rails looks at the action name, locale, format, variant, etc. to figure out exactly what to render. If a template doesn't exist, or is not in the correct format or variant, Rails will raise the ActionController::UnknownFormat
error and render 204 No Content
.
You don’t need to use all these variations daily, but knowing they exist gives you tools for every situation.
Render to String
Sometimes you want to use your view rendering engine to generate a string, without actually sending it as a response. That’s what render_to_string
is for.
Common use cases include:
- Sending HTML or text emails using the same templates as your web views.
- Generating a downloadable PDF from a view.
- Logging or caching the rendered output.
html = render_to_string(template: "invoices/show", layout: false)
PdfService.generate(html)
Keep in mind that render_to_string
still runs the full view rendering stack (layouts, helpers, partials), so it’s not free performance-wise, but it's often the cleanest way to reuse your existing views.
Rendering Partials with Collections
Rendering collections is a performance-friendly way to output many items using a single partial. If you’re not using it yet, you should.
<%= render partial: "posts/post", collection: @posts %>
Rails will call _post.html.erb
for each item in @posts
, assigning each one to a post
local.
That's a wrap. I hope you found this article helpful and you learned something new.
As always, if you have any questions or feedback, didn't understand something, or found a mistake, please leave a comment below or send me an email. I reply to all emails I get from developers, and I look forward to hearing from you.
If you'd like to receive future articles directly in your email, please subscribe to my blog. Your email is respected, never shared, rented, sold or spammed. If you're already a subscriber, thank you.