# HtmlToPdf — Rails Controller Actions to PDF via Headless Chrome
> HtmlToPdf renders any Rails controller action to a downloadable PDF exactly as the browser would
> — real CSS, real layout, real fonts — via a pooled headless Chrome instance (driven over the
> Chrome DevTools Protocol through the `ferrum` gem) instead of an abandoned wkhtmltopdf binary.
> Wire-up is `include HtmlToPdf` + `around_action :html_to_pdf` on a controller; it only takes over
> when the request's format is `pdf`, leaving every other request untouched.
It's a real `around_action`, not a `before_action` that re-renders the action on a throwaway
controller instance — every other callback, instance variable, and `rescue_from` behaves exactly
as it would for an ordinary request. See "Guides" below for the Rails internals reason that
distinction matters.
## Getting started
- [README](https://github.com/ramlaxmanyadav/htmlToPdf/blob/master/README.md): installation,
wiring, the full `pdf_options`/`print_options` reference, and configuration.
- [llms-full.txt](https://ramlaxmanyadav.github.io/htmlToPdf/llms-full.txt): this file plus the
complete content of the README and the architecture/Rails-integration/security docs,
concatenated into one fetch — for agents that prefer a single request over following links.
- [gemspec](https://github.com/ramlaxmanyadav/htmlToPdf/blob/master/htmlToPdf.gemspec): gem
metadata, version, and dependency bounds.
- [CHANGELOG](https://github.com/ramlaxmanyadav/htmlToPdf/blob/master/CHANGELOG.md): what shipped
in each version.
## Documentation
- [Architecture](https://github.com/ramlaxmanyadav/htmlToPdf/blob/master/docs/architecture.md):
why headless Chrome instead of wkhtmltopdf, the full request lifecycle (sequence diagram), the
`BrowserPool` design (one Chrome process per Ruby process, isolated context per render), and the
`print_options` precedence/validation model.
- [HtmlToPdf in Rails](https://github.com/ramlaxmanyadav/htmlToPdf/blob/master/docs/rails-integration.md):
callback ordering, a custom layout/filename per request, Chrome in Docker, and the `asset_host`
gotcha that leaves a PDF unstyled with no error anywhere.
- [Security](https://github.com/ramlaxmanyadav/htmlToPdf/blob/master/docs/security.md): exactly
what's safe to expose on a public action — the validated print-options surface, why
`header_template`/`footer_template` are opt-in only, and the `javascript_delay` cap.
## Guides
- [Headless Chrome vs. wkhtmltopdf for Rails PDF generation](https://ramlaxmanyadav.github.io/htmlToPdf/headless-chrome-vs-wkhtmltopdf.html):
why an unmaintained, years-frozen WebKit build is the wrong foundation in 2026, and what actually
changes — and what it costs — when you switch to a real, currently-maintained browser engine.
- [The DoubleRenderError trap: rendering a Rails view to PDF](https://ramlaxmanyadav.github.io/htmlToPdf/rails-double-render-pdf.html):
the exact Rails internals reason (`ActionController::Metal#response=` marking a controller as
already-rendered) a before_action-based "rebuild the controller and re-run the action" design
raises `DoubleRenderError` in a real app but not in a quick isolated test, and why the fix has to
be an `around_action`.
- [Choosing a Rails HTML-to-PDF gem](https://ramlaxmanyadav.github.io/htmlToPdf/choosing-a-rails-pdf-gem.html):
HtmlToPdf vs. Grover vs. WickedPdf/PDFKit vs. Prawn, contender by contender.
## Quick start
```ruby
# Gemfile
gem "htmlToPdf"
```
```ruby
class ApplicationController < ActionController::Base
include HtmlToPdf
around_action :html_to_pdf
end
```
```erb
<%= link_to "Download PDF", invoice_path(@invoice, format: :pdf, pdf_options: {
title: "invoice-#{@invoice.number}",
layout: "pdf"
}) %>
```
## What it does
- Renders the controller's real view — the same template and (by default) the same layout the app
already serves for an HTML request, not a separate PDF-specific template.
- One pooled `Ferrum::Browser` process per Ruby process, lazily started, auto-restarted if Chrome
crashes; each conversion gets its own isolated browser context.
- Chrome's full print-to-PDF surface: landscape/portrait, named paper sizes or explicit
width/height (numbers or unit strings — `mm`/`cm`/`in`/`px`), margins, scale, page ranges,
header/footer templates, a `javascript_delay` for content that renders asynchronously.
- A narrow, validated `print_options` surface — every option is a checked number/enum/boolean
except `header_template`/`footer_template` (raw HTML), which are filtered out of
request-supplied options unless `config.allow_request_templates` is explicitly enabled.
- Runtime dependency: `ferrum` only (plus `activesupport`, already present in any Rails app) — no
Node.js, no Puppeteer.
## Links
- [GitHub repo](https://github.com/ramlaxmanyadav/htmlToPdf)
- [RubyGems](https://rubygems.org/gems/htmlToPdf)
- [This site's index](https://ramlaxmanyadav.github.io/htmlToPdf/)