Ruby Guide

HTML to PDF in Ruby

Generate PDFs from HTML in Rails or Sinatra — without WickedPDF, PDFKit, or wkhtmltopdf. No system binary to install, no gem conflicts. Works anywhere Ruby runs.

Why not WickedPDF or PDFKit?

WickedPDF and PDFKit are wrappers around wkhtmltopdf — a binary that uses an old WebKit build from ~2012. It doesn't support Flexbox, CSS Grid, or modern fonts properly. You also need to install the wkhtmltopdf system binary in every environment: local, CI, staging, production, Docker. Version mismatches between environments are common.

Papyr uses modern Chromium. No system binary, no version drift, no layout surprises.

Plain Ruby

Works with just the standard library — no gems required.

Ruby
require "net/http"
require "json"

def html_to_pdf(html)
  uri = URI("https://api.getpapyr.dev/v1/render/pdf")
  req = Net::HTTP::Post.new(uri)
  req["Authorization"] = "Bearer #{ENV['PAPYR_API_KEY']}"
  req["Content-Type"]  = "application/json"
  req.body = { html: html, options: { format: "A4" } }.to_json

  res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
  raise "PDF error: #{res.code}" unless res.is_a?(Net::HTTPSuccess)
  res.body
end

# Save to disk
File.binwrite("output.pdf", html_to_pdf("<h1>Hello, PDF!</h1>"))

Rails controller

Render your ERB template to a string, send to Papyr, stream back as a file download.

Ruby on Rails
# app/controllers/invoices_controller.rb
require "net/http"
require "json"

class InvoicesController < ApplicationController
  def pdf
    invoice = Invoice.find(params[:id])

    # Render your ERB/Haml template to an HTML string
    html = render_to_string(
      template: "invoices/pdf",
      layout: "pdf",
      locals: { invoice: invoice }
    )

    response = papyr_render(html)

    send_data response,
      filename: "invoice-#{invoice.number}.pdf",
      type: "application/pdf",
      disposition: "attachment"
  end

  private

  def papyr_render(html)
    uri = URI("https://api.getpapyr.dev/v1/render/pdf")
    req = Net::HTTP::Post.new(uri)
    req["Authorization"] = "Bearer #{ENV['PAPYR_API_KEY']}"
    req["Content-Type"]  = "application/json"
    req.body = { html: html }.to_json

    res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
    raise "PDF generation failed" unless res.is_a?(Net::HTTPSuccess)
    res.body
  end
end

Rails + Faraday + Active Storage

Generate and attach the PDF directly to an Active Storage record — no temp files.

Rails + Faraday
# Using Faraday gem (common in Rails apps)
require "faraday"

def html_to_pdf(html, options = {})
  conn = Faraday.new("https://api.getpapyr.dev") do |f|
    f.request  :json
    f.response :raise_error
  end

  response = conn.post("/v1/render/pdf") do |req|
    req.headers["Authorization"] = "Bearer #{ENV['PAPYR_API_KEY']}"
    req.body = { html: html, options: { format: "A4" }.merge(options) }
  end

  response.body
end

# Store in Active Storage / S3
pdf = html_to_pdf(html, landscape: true)
invoice.pdf_file.attach(
  io: StringIO.new(pdf),
  filename: "invoice-#{invoice.number}.pdf",
  content_type: "application/pdf"
)

Get your API key

Free tier: 100 PDFs/month. No credit card required.