Guide
Generate a PDF from HTML in Ruby
You have some HTML (an invoice, a statement, a certificate) and you need it as a PDF file. The standard Ruby answers involve shelling out to a wkhtmltopdf binary or pulling in a gem like Prawn that has you drawing the document by hand. This guide shows the lighter path: one Net::HTTP POST from the standard library. No binary, no gem install.
The minimal script
The endpoint is POST https://api.pdfpipe.xyz/v1/pdf. Send a JSON body with your HTML, attach a bearer token, and write the response bytes straight to a file.
require "net/http"
require "json"
require "uri"
ENDPOINT = URI("https://api.pdfpipe.xyz/v1/pdf")
html = <<~HTML
<html>
<body style="font-family: sans-serif; padding: 40px">
<h1>Invoice #1042</h1>
<p>Amount due: $420.00</p>
</body>
</html>
HTML
request = Net::HTTP::Post.new(ENDPOINT)
request["Authorization"] = "Bearer #{ENV.fetch('PDFPIPE_API_KEY')}"
request["Content-Type"] = "application/json"
request.body = JSON.generate(
html: html,
options: { format: "A4" }
)
response = Net::HTTP.start(
ENDPOINT.hostname, ENDPOINT.port, use_ssl: true, read_timeout: 30
) { |http| http.request(request) }
raise "PDFPipe error #{response.code}: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
File.binwrite("invoice-1042.pdf", response.body)
puts "Saved invoice-1042.pdf"That is the complete feature in about 25 lines. Store the key in an environment variable and never commit it. The read_timeout prevents a hung connection from blocking your process indefinitely.
Reusable service module
In a larger project, wrap the call in a module so you can reuse it from anywhere and add error handling in one place.
# lib/pdf_pipe.rb
require "net/http"
require "json"
require "uri"
module PdfPipe
ENDPOINT = URI("https://api.pdfpipe.xyz/v1/pdf")
private_constant :ENDPOINT
# Returns the raw application/pdf bytes.
# Raises RuntimeError if the API returns a non-2xx response.
def self.render(html, format: "A4")
request = Net::HTTP::Post.new(ENDPOINT)
request["Authorization"] = "Bearer #{ENV.fetch('PDFPIPE_API_KEY')}"
request["Content-Type"] = "application/json"
request.body = JSON.generate(
html: html,
options: { format: format }
)
response = Net::HTTP.start(
ENDPOINT.hostname, ENDPOINT.port,
use_ssl: true,
open_timeout: 5,
read_timeout: 30
) { |http| http.request(request) }
raise "PDFPipe error #{response.code}: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
response.body
end
endThen call it from anywhere in your application:
require_relative "lib/pdf_pipe"
pdf = PdfPipe.render(html, format: "A4")
File.binwrite("output.pdf", pdf)Rendering from a URL
If your document already lives at a URL, pass that instead of inlining the HTML.
request.body = JSON.generate(
url: "https://example.com/report/42",
options: { format: "A4" }
)Why not wkhtmltopdf or Prawn?
wkhtmltopdf renders with a frozen 2015-era WebKit. Flexbox often breaks, grid is not supported, and the project is archived with no further fixes. Prawn is reliable but you describe every line of the document in code: there is no HTML input. Both approaches add a native binary or a gem with C extensions to your dependency graph.
| Concern | wkhtmltopdf / Prawn | Net::HTTP to PDFPipe |
|---|---|---|
| System dependency | Binary or C extension gem | None, standard library only |
| Modern CSS | wkhtmltopdf breaks on flex/grid | Full support, current engine |
| Input format | HTML (wkhtmltopdf) or draw-by-hand (Prawn) | HTML or URL |
| Data leaves server | No | Yes, sent over HTTPS |
Paste your own HTML into the live playground on the home page to see the PDF render instantly, then read the docs for every option.
See pricing →