PDFPipe

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.

ruby
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.

ruby
# 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
end

Then call it from anywhere in your application:

ruby
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.

ruby
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.

Concernwkhtmltopdf / PrawnNet::HTTP to PDFPipe
System dependencyBinary or C extension gemNone, standard library only
Modern CSSwkhtmltopdf breaks on flex/gridFull support, current engine
Input formatHTML (wkhtmltopdf) or draw-by-hand (Prawn)HTML or URL
Data leaves serverNoYes, 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 →