HTML to PDF
in Ruby.
Most Rails apps generating PDFs still shell out to wkhtmltopdf through WickedPDF. It works until the design uses flexbox, or until the buildpack stops shipping the binary.
The call
Post your markup with an API key. The PDF comes back in the same response, base64 encoded by default. Nothing is written to disk on our side.
require "base64"
require "json"
require "net/http"
uri = URI("https://api.html4pdf.com/generate-pdf")
res = Net::HTTP.post(
uri,
{
html: "<h1>Invoice INV-1042</h1><p>Total: $1,645.40</p>",
options: { format: "A4", printBackground: true }
}.to_json,
"Authorization" => "Bearer #{ENV.fetch('HTML4PDF_KEY')}",
"Content-Type" => "application/json"
)
data = JSON.parse(res.body)
File.binwrite("invoice.pdf", Base64.decode64(data["pdf"]))From a Rails controller
Render the view you already have, post it, and send the bytes. No temporary files and no binary in your slug.
def show
html = render_to_string(template: "invoices/show", layout: "pdf", formats: [:html])
res = Net::HTTP.post(
URI("https://api.html4pdf.com/generate-pdf"),
{ html: html, responseFormat: "binary" }.to_json,
"Authorization" => "Bearer #{Rails.application.credentials.html4pdf_key}",
"Content-Type" => "application/json"
)
send_data res.body, filename: "#{@invoice.number}.pdf", type: "application/pdf"
endWhat comes back
The default response is JSON with the document base64 encoded, the credits you have left, and how long the render took. Send responseFormat as binary instead and the body is the PDF itself, with a Content-Type of application/pdf.
{
"success": true,
"pdf": "JVBERi0xLjQKJeLjz9MKMyAwIG9...",
"creditsRemaining": 9,
"metadata": { "size": 42813, "processingTime": 2841 }
}A 402 means you are out of credits, a 401 means the key is wrong, and a 422 means the markup could not be rendered. Everything else is worth retrying once.
The usual Ruby libraries
Worth knowing before you add a dependency, because for a lot of documents one of these is the right answer and costs nothing.
| Library | What it is | The trade-off |
|---|---|---|
| WickedPDF | Renders a Rails view through the wkhtmltopdf binary. | Familiar and Rails-native. You vendor a native binary per platform, and the engine behind it is unmaintained and pre-flexbox. |
| Grover | Drives Puppeteer from Ruby. | Chrome-accurate output, but it needs Node and Chrome alongside your Ruby app. |
| Prawn | Builds PDFs in Ruby with a drawing DSL. | Precise and dependency-free. No HTML, so a layout change means writing code. |
Ruby: common questions
How do I generate a PDF from a Rails view?
Use render_to_string to turn the view into HTML, post that string to a rendering API, and send_data the bytes back with a PDF content type. The controller above is the whole thing.
Is WickedPDF still a good choice?
It is fine for documents that stuck to simple CSS years ago. For new work, the unmaintained wkhtmltopdf binary underneath it is the problem, not the gem.
Does this work on Heroku?
Yes, and it removes the buildpack you currently need for wkhtmltopdf.
Can I use Faraday or HTTParty instead?
Yes. Any HTTP client works. Net::HTTP is used here only because it needs no gem.