HTML to Image in Ruby on Rails
Render HTML to a PNG from Rails using Faraday or Net::HTTP.
Best for: Rails apps generating receipts on payment, weekly digest images, or any Active Job background render. Net::HTTP works without a gem; Faraday gives you middleware for retries and logging.
Setup
First, add your API key to your credentials or environment variables:
# config/credentials.yml.enc
html2img:
api_key: your-api-key
Or using environment variables:
# .env
HTML2IMG_API_KEY=your-api-key
API Response Format
Every successful request returns a JSON body with a url pointing to the generated image:
{
"success": true,
"credits_remaining": 95,
"id": "abc123",
"url": "https://i.html2img.com/abc123.png"
}
You can either return the url directly to the client, or download the image from it if you need a local copy.
HTML to Image Example
Here’s a simple example of converting HTML to an image:
require 'net/http'
require 'json'
class ImagesController < ApplicationController
def generate
uri = URI('https://app.html2img.com/api/html')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request['X-API-Key'] = Rails.application.credentials.html2img[:api_key]
request.body = {
html: '<div style="padding: 20px; background: #f0f0f0;">
<h1>Hello from Rails!</h1>
<p>Generated at ' + Time.current.to_s + '</p>
</div>',
css: 'h1 { color: #2563eb; } p { color: #4b5563; }',
width: 800,
height: 600
}.to_json
response = http.request(request)
data = JSON.parse(response.body)
if response.is_a?(Net::HTTPSuccess) && data['success']
render json: {
success: true,
url: data['url'],
id: data['id'],
credits_remaining: data['credits_remaining']
}
else
render json: {
success: false,
message: data['message'] || 'Unknown error'
}, status: response.code
end
end
end
Screenshot Example
Taking a screenshot of a webpage:
class ImagesController < ApplicationController
def screenshot
uri = URI('https://app.html2img.com/api/screenshot')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request['X-API-Key'] = Rails.application.credentials.html2img[:api_key]
request.body = {
url: 'https://example.com',
width: 1200,
height: 800,
dpi: 2,
fullpage: true
}.to_json
response = http.request(request)
data = JSON.parse(response.body)
if response.is_a?(Net::HTTPSuccess) && data['success']
render json: {
success: true,
url: data['url']
}
else
render json: {
success: false,
message: data['message'] || 'Unknown error'
}, status: response.code
end
end
end
Using with ERB Templates
You can also generate images from ERB templates:
class InvoicesController < ApplicationController
def generate_image
html = render_to_string(
template: 'invoices/template',
layout: false,
locals: { order: Order.find(params[:id]) }
)
uri = URI('https://app.html2img.com/api/html')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request['X-API-Key'] = Rails.application.credentials.html2img[:api_key]
request.body = {
html: html,
width: 800,
height: 600
}.to_json
response = http.request(request)
data = JSON.parse(response.body)
if response.is_a?(Net::HTTPSuccess) && data['success']
render json: {
success: true,
url: data['url']
}
else
render json: {
success: false,
message: data['message'] || 'Unknown error'
}, status: response.code
end
end
end
Using ActiveStorage
If you want to persist the image using ActiveStorage, download it from the returned URL and attach it to your model:
require 'open-uri'
class Order < ApplicationRecord
has_one_attached :invoice_image
end
class InvoicesController < ApplicationController
def generate_image
order = Order.find(params[:id])
html = render_to_string(
template: 'invoices/template',
layout: false,
locals: { order: order }
)
uri = URI('https://app.html2img.com/api/html')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request['X-API-Key'] = Rails.application.credentials.html2img[:api_key]
request.body = {
html: html,
width: 800,
height: 600
}.to_json
response = http.request(request)
data = JSON.parse(response.body)
if response.is_a?(Net::HTTPSuccess) && data['success']
# Download the image from the returned URL and attach it
URI.open(data['url']) do |image|
order.invoice_image.attach(
io: image,
filename: "invoice-#{order.id}.png",
content_type: 'image/png'
)
end
render json: {
success: true,
url: url_for(order.invoice_image)
}
else
render json: {
success: false,
message: data['message'] || 'Unknown error'
}, status: response.code
end
end
end
Make sure you have ActiveStorage set up and configured for your Rails application if you want to use the attachment feature.
Routes Configuration
Add these routes to your config/routes.rb:
Rails.application.routes.draw do
post 'generate_image', to: 'images#generate'
post 'take_screenshot', to: 'images#screenshot'
post 'generate_invoice_image', to: 'invoices#generate_image'
end
Handle the API key securely and never commit it to version control.
Common patterns
Faraday with retries
require 'faraday'
require 'faraday/retry'
class Html2ImgClient
def initialize(api_key)
@conn = Faraday.new(url: 'https://app.html2img.com/api/') do |f|
f.request :retry, max: 3, interval: 0.5, backoff_factor: 2,
exceptions: [Faraday::ConnectionFailed, Faraday::TimeoutError]
f.request :json
f.response :json
f.headers['X-API-Key'] = api_key
f.adapter Faraday.default_adapter
end
end
def render_html(html, options = {})
@conn.post('html', { html: html }.merge(options)).body['url']
end
def render_template(slug, payload)
@conn.post("v1/templates/#{slug}", payload).body['url']
end
end
Use this client from any Active Job for background renders. Failures retry automatically with exponential backoff.
Templates in Rails
Use a named template when you have structured data and want a tested image. Here is the Invoice Image template:
require 'net/http'
require 'json'
uri = URI('https://app.html2img.com/api/v1/templates/invoice-image')
request = Net::HTTP::Post.new(uri)
request['X-API-Key'] = ENV['HTML2IMG_API_KEY']
request['Content-Type'] = 'application/json'
request.body = {
invoice_number: 'INV-2026-0042',
business_name: 'Coastline Coffee Co',
client_name: 'Riverside Bakery',
items: [{ description: 'Wholesale beans', quantity: '50', unit_price: '$15.00', amount: '$750.00' }],
total: '$750.00'
}.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }
url = JSON.parse(response.body)['url']
The returned url is hosted on i.html2img.com.