ERB Templates Outside of Rails
ERB (Embedded RuBy) ships as part of Ruby’s standard library. You do not need Rails, Bundler, or any external gem to start using it. It lets you embed Ruby expressions and control flow directly inside text templates, making it a versatile tool for code generation, email mailers, static site builders, and configuration file rendering.
What ERB Is
ERB processes templates that contain three kinds of tags:
<% %>— runs Ruby code silently (no output)<%= %>— runs Ruby code and inserts the result into the template<%# %>— a comment, completely ignored
Everything else in the template passes through as literal text. This makes ERB useful for any task where you need to mix static text with dynamic values.
Basic Usage
Create an ERB object from a template string and call #result to get the rendered output:
require "erb"
template = ERB.new(<<~TEMPLATE)
Hello, <%= name %>! You have <%= count %> messages.
TEMPLATE
name = "Alice"
count = 3
puts template.result
# => Hello, Alice! You have 3 messages.
Calling #result without arguments uses the current binding — the set of local variables visible at the call site.
Passing a Binding Explicitly
Sometimes you want to render a template in a different context. Pass a binding explicitly using binding or by building a custom binding:
def render_user_report(user)
template = ERB.new(<<~TEMPLATE)
User: <%= user[:name] %>
Email: <%= user[:email] %>
Role: <%= user[:role] %>
TEMPLATE
template.result(binding)
end
user = { name: "Bob", email: "bob@example.com", role: "admin" }
puts render_user_report(user)
# => User: Bob
# => Email: bob@example.com
# => Role: admin
You can also build a binding explicitly using Binding.new (available in Ruby 3.0+) or pass the local bindings hash:
def render_with_locals(template_str, locals)
b = binding
locals.each { |k, v| b.local_variable_set(k, v) }
ERB.new(template_str).result(b)
end
puts render_with_locals("Welcome, <%= name %>!", { name: "Carol" })
# => Welcome, Carol!
Trim Mode
By default, ERB preserves the whitespace around tags. The <% and %> trim mode flag changes this behaviour, which matters when generating source code or structured text where you do not want blank lines from template directives.
| Trim Mode | Behaviour |
|---|---|
% (no dash) | Trims leading whitespace before the tag and the newline after |
-%> | Trims the newline after the closing tag (trailing newline removed from the output line) |
<%- | Trims leading whitespace before the opening tag |
<% combined with -%> | Trims on both sides |
# Default — preserves surrounding whitespace
template = ERB.new("Line 1\n<%= 1 + 1 %>\nLine 3")
puts template.result
# => Line 1
# => 2
# => Line 3
# Trim mode: removes blank line caused by the tag
template = ERB.new("Line 1\n<% 1 + 1 %>\nLine 3", trim: "%")
puts template.result
# => Line 1
# => Line 3
# Trim both leading whitespace and trailing newline
template = ERB.new(" <%- 1 + 1 %>\nLine 3", trim: "-%")
puts template.result
# => Line 3
The trim mode is especially useful when generating Ruby code, HTML, or any structured format where extra blank lines would cause problems.
Security — Avoiding Injection
Raw output inside an ERB template can open you up to injection attacks. When the template produces HTML and user input flows into it unescaped, an attacker can inject arbitrary markup or scripts.
Ruby’s standard library provides ERB::Util.html_escape (aliased as h) to escape HTML-sensitive characters:
require "erb"
require "erb/util"
template = ERB.new(<<~TEMPLATE)
<p>Welcome, <%= ERB::Util.html_escape(username) %></p>
TEMPLATE
username = "<script>alert('xss')</script>"
puts template.result
# => <p>Welcome, <script>alert('xss')</script></p>
If you are building plain text (not HTML), use CGI.escape or URI.encode_www_form_component as appropriate. The rule is simple: always escape output when the template produces a format where special characters have meaning.
Real-World Use Cases
Email Mailers
ERB templates work well for composing transactional emails without pulling in a framework:
require "erb"
class UserMailer
def welcome_email(user)
template = ERB.new(File.read("templates/welcome.txt.erb"))
template.result_with_hash(
name: user[:name],
url: user[:verification_url]
)
end
end
mailer = UserMailer.new
body = mailer.welcome_email(name: "Dana", verification_url: "https://example.com/verify/abc")
puts body
#result_with_hash accepts a hash and makes those keys available as local variables inside the template — a cleaner interface than manually managing bindings.
Static Site Generator
ERB can power a simple static site. Store templates in a directory and render Markdown or HTML files with interpolated frontmatter:
require "erb"
require "yaml"
Dir.glob("content/**/*.md").each do |file|
raw = File.read(file)
parts = raw.split(/^---$/, 3)
meta = YAML.safe_load(parts[1])
body = ERB.new(parts[2]).result_with_hash(meta)
output = File.join("public", file.sub("content/", ""))
File.write(output.sub(".md", ".html"), body)
end
Code Generation
Generating Ruby source code from ERB templates is a clean way to build repetitive boilerplate. Using the -%> trim mode keeps the generated code tidy:
require "erb"
def generate_accessor(name)
template = ERB.new(<<~RUBY, trim: "-%")
def <%= name %>
@<%= name %>
end
def <%= name %>=(value)
@<%= name %> = value
end
RUBY
template.result
end
puts generate_accessor(:title)
# => def title
# => @title
# => end
# =>
# => def title=(value)
# => @title = value
# => end
You can extend this pattern to generate model classes, database migrations, or configuration files based on schemas.
See Also
- The ERB Module — Full reference for the ERB class, trim modes, and methods
- The
requireMethod — How to load templates from disk using Ruby’s load path - Lambdas and Procs — Using closures with ERB for custom logic inside templates