ERB Templates Outside of Rails

· 5 min read · Updated March 20, 2026 · intermediate
ruby erb templates stdlib

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 ModeBehaviour
% (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, &lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;</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