# FerrumPdf renders Rails pages to PDF through headless Chrome, without Node

> FerrumPdf is a MIT licensed Ruby gem that turns HTML or a URL into a PDF or a screenshot by driving headless Chrome through Ferrum, the pure Ruby implementation of the Chrome DevTools Protocol, and it drops the Node.js and Puppeteer dependencies that Grover needs. It suits Rails applications that need printable or shareable output from views they already have, and it needs a Chrome installation on the machine that renders.

**excid3/ferrum_pdf** — A PDF generator for Rails using Ferrum & headless Chrome

- Repository: https://github.com/excid3/ferrum_pdf
- Stars: 534 · Forks: 33
- Language: Ruby
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/excid3-ferrum-pdf

## A Grover alternative with the Node.js dependency removed

The positioning in the repository description is plain: a PDF generator for Rails using Ferrum and headless Chrome. The readme adds the reason it exists, saying it was inspired by Grover but without the Node.js and Puppeteer dependencies. That single difference is the whole design. Grover is the Rails PDF gem that FerrumPdf was inspired by, and the difference the readme highlights is that FerrumPdf avoids the Node.js and Puppeteer dependencies, so a Rails deployment does not need a second runtime alongside Ruby. Ferrum is a pure Ruby implementation of the Chrome DevTools Protocol, the same protocol Ferrum itself speaks to the browser, so FerrumPdf keeps the entire path inside Ruby and asks only for a Chrome binary. For a team that has already decided against running Node in production, that removes the objection that would otherwise end the evaluation of a PDF library.

## Two entry points, and one of them is a Rails renderer

The usage section offers two ways to reach each output, and the pairing is the same for PDFs and screenshots. Inside a controller you can use the ferrum_pdf renderer or the ferrum_screenshot renderer, both taking an empty options hash, and outside a controller there are the class methods FerrumPdf.render_pdf and FerrumPdf.render_screenshot. The renderer form is the one that fits an ordinary Rails view, because it renders the current action in a different format.

```ruby
def show
  respond_to do |format|
    format.html
    format.pdf { render ferrum_pdf: {}, disposition: :inline, filename: "example.pdf" }
  end
end
```

The same pattern with a png format produces a screenshot instead of a document.

```ruby
def show
  respond_to do |format|
    format.html
    format.png { render ferrum_screenshot: {}, disposition: :inline, filename: "example.png" }
  end
end
```

Both renderers accept disposition and filename, so the response can be shown inline in a browser or sent as a download, and the file name is set by you rather than derived from the action.

## The controller renderer can pick a template and a layout

The second controller example is the one to copy when a PDF needs to look different from the HTML page, since it names a template, a layout and the header and footer behaviour.

```ruby
def show
  render ferrum_pdf: {
    display_header_footer: true,
    header_template: FerrumPdf::DEFAULT_HEADER_TEMPLATE,
    footer_template: FerrumPdf::DEFAULT_FOOTER_TEMPLATE
  },
  layout: "pdf",
  template: "pdf",
  disposition: :inline,
  filename: "example.pdf"
end
```

The readme explains the mechanism: this renders the template to a string with render_to_string in Rails, then passes that string along to Chrome. The consequence is that the template is rendered by Rails first, in the normal request context, and only the resulting HTML is handed to the browser. That is why a Rails layout, a helper and a current_user check all work inside the PDF template, and it is also why anything that depends on client side rendering after load will not appear unless the page waits for it. The header and footer templates come from constants the gem provides, FerrumPdf::DEFAULT_HEADER_TEMPLATE and FerrumPdf::DEFAULT_FOOTER_TEMPLATE, so you do not have to write your own before you can try the feature.

## The standalone methods take HTML or a URL

For everything outside a controller the entry point is a class method that accepts either a URL or a string of HTML.

```ruby
FerrumPdf.render_pdf(html: content)
FerrumPdf.render_pdf(url: "https://google.com")
```

```ruby
FerrumPdf.render_screenshot(html: content)
FerrumPdf.render_screenshot(url: "https://google.com")
```

The HTML form is what a background job or a mailer uses, and the URL form is what a service that captures a remote page uses. Two options sit alongside them and are worth noticing. display_url sets the base URL used to resolve relative paths when you pass HTML, and it defaults to FerrumPdf.config.page_options.display_url, which means a relative asset path in your HTML only resolves if the base URL is configured. authorize takes a user and password for basic authentication against the target. wait_for_idle_options sets the network idle conditions, and timeout_if_open_connections decides whether the render gives up while connections are still open. The viewport option sets width, height and the device scale factor, so the same page can be rendered for a wide screen and for a retina display.

## The PDF options are the printToPDF options, with a Ruby shape

The options list is long, and it is essentially the Chrome DevTools Protocol printToPDF call with Ruby keyword arguments. The readme points at the Chrome DevTools Protocol documentation for the printToPDF method and at Ferrum's own pdf options page for the full set, which is the right place to look for the complete set rather than the readme's summary. The shape is worth understanding anyway. pdf_options holds the paper settings: landscape for orientation, scale for the rendering, format, paper_width and paper_height in inches, and page_ranges to print a selection such as "1-5, 8 11-13". The margins are in inches and default to 1cm, with margin_top, margin_bottom, margin_left and margin_right. Then there is display_header_footer, print_background for printing background graphics, and header_template and footer_template as HTML. The comment in the readme lists the variables those templates can use, including a date class for the formatted print date, a title class for the document title, a url class for the document location, a pageNumber class for the current page number and a totalPages class for the total. Those class names are Chrome's own template variables rather than anything the gem defines, which is why they are documented as CSS classes.

## Screenshots have their own smaller option set

The screenshot options are a compact group nested under screenshot_options, and they cover the cases where a picture rather than a document is what you need.

```ruby
FerrumPdf.render_screenshot(
  url: "https://example.com/page", # Provide a URL to the content

  html: content, # or provide HTML
  display_url: request.original_url, # Base URL for resolving relative paths in :html. Defaults to FerrumPdf.config.page_options.display_url.

  screenshot_options: {
    format: "png" # or "jpeg"
    quality: nil # Integer 0-100 works for jpeg only
    full: true # Boolean whether you need full page screenshot or a viewport
    selector: nil # String css selector for given element, optional
    area: nil # Hash area for screenshot, optional. {x: 0, y: 0, width: 100, height: 100}
    scale: nil # Float zoom in/out
    background_color: nil # Ferrum::RGBA.new(0, 0, 0, 0.0)
  }
)
```

Six of those cover most cases. format chooses png or jpeg, quality is an integer from 0 to 100 and works for jpeg only, full decides whether you capture the whole page or just the viewport, selector captures one element by CSS selector, area captures a rectangle with x, y, width and height, and scale is a zoom factor. The background colour is expressed as a Ferrum::RGBA value rather than a CSS string, which tells you the option set is Ferrum's own rather than a Chrome one, and a transparent background is the default of Ferrum::RGBA.new(0, 0, 0, 0.0).

## Defaults live in one configure block, split by kind of render

The configuration section is where a deployment sets its policy once rather than repeating options at every call site, and the structure is three namespaces: page_options for page loads, pdf_options for PDF renders and screenshot_options for screenshot renders.

```ruby
FerrumPdf.configure do |config|
  config.page_options.authorize = { user: "username", password: "password" }
  config.page_options.wait_for_idle_options = { timeout: 90 }
  config.page_options.retries = 3
  config.page_options.viewport = { width: 1200, height: 800, scale_factor: 3 }
  config.page_options.javascript_enabled = false
  config.page_options.display_url = "https://example.org"

  config.pdf_options.margin_top = 0.2
  config.pdf_options.margin_bottom = 0.2
  config.pdf_options.margin_left = 0.2
  config.pdf_options.margin_right = 0.2

  config.screenshot_options.format = :png
  config.screenshot_options.full = false
end
```

Three of those defaults matter more than the rest in a real deployment. javascript_enabled set to false stops scripts in the page from running, and the documentation is careful about the limit of that switch, since it does not block network requests for images, stylesheets and iframes, nor meta refresh redirects. retries of 3 is what keeps a transient browser failure from turning into a user-facing error. And wait_for_idle_options with a timeout of 90 is the setting that decides how long a render will wait for a page to settle, which on a page with a polling XHR endpoint can be the difference between a document and a blank one. The readme then points at a further section on configuring the browser itself, which is where the Chrome process options live.

## Where FerrumPdf is the wrong choice

The first wrong case is a document that is mostly text and tables, because a browser based renderer gives you the fidelity of a web page rather than the control of a template engine, and a template engine is both faster and easier to paginate correctly. The second is a deployment that cannot guarantee a Chrome binary on the rendering host, since the installation step begins with making sure Chrome is installed, and a container image without a browser is a common outcome of slimming an image. The third is a system where the page depends on long polling or on client side rendering that never reaches a stable state, because the render waits for the network to go idle and the wait_for_idle_options settings exist precisely because that wait can fail. The fourth is a team that wants to produce PDFs on a machine with no display and no headless Chrome, since the whole approach assumes a browser process. The fifth is an application that already has a Node.js rendering service it is happy with, since the point of FerrumPdf is to remove that dependency, and if the dependency is not a problem then the change buys little. The last is a request for a format the browser does not produce, since the output here is what Chrome prints to PDF.

## Conclusion

Rails teams that need a PDF or a screenshot of a view they already render as HTML, and that would rather not run a Node.js service alongside their Ruby application, are the people FerrumPdf is for. It is the wrong choice for a Rails application that renders most of its pages server side and only needs a document, because a template engine gives you that without a browser, and it is the wrong choice for a team without a Chrome binary available to the rendering process. Check three things before you commit: that Chrome is installed where the gem runs, since the installation step says to make sure Chrome is installed first, that the options you need are exposed, since the full option list is delegated to the Chrome DevTools Protocol printToPDF documentation and Ferrum's own pdf options page, and how you will manage the browser process in production, since the readme has a section on configuring the browser itself. The licence is MIT, the last push was on 2026-10-20, and the newest release is v3.2.0 from 2026-09-14.

## FAQ

### What is FerrumPdf and how does it differ from Grover?

It is a PDF generator for Rails using Ferrum and headless Chrome. The readme says it was inspired by Grover but without the Node.js and Puppeteer dependencies, so the rendering path stays in Ruby and only needs a Chrome installation.

### How do I install FerrumPdf in a Rails application?

Make sure Chrome is installed, then run bundle add "ferrum_pdf" or add the gem to your Gemfile manually. The gem is published under the name ferrum_pdf with a version of 3.2.0 in the latest release.

### How do I render a PDF from a Rails controller with FerrumPdf?

Use the ferrum_pdf renderer inside a respond_to block, for example format.pdf { render ferrum_pdf: {}, disposition: :inline, filename: "example.pdf" }. You can also pass template, layout and the pdf_options such as display_header_footer with header_template and footer_template.

### Which options control PDF paper size and margins in FerrumPdf?

The pdf_options group holds landscape, scale, format, paper_width and paper_height in inches, page_ranges such as "1-5, 8 11-13", and margin_top, margin_bottom, margin_left and margin_right in inches, which default to 1cm. The readme points at the Chrome DevTools Protocol printToPDF documentation and Ferrum's pdf options page for the full list.

### How does FerrumPdf configure defaults for a whole application?

With a FerrumPdf.configure block that has three namespaces: page_options for page loads such as authorize, wait_for_idle_options, retries, viewport, javascript_enabled and display_url, pdf_options for document settings such as the margins, and screenshot_options for image settings such as format and full.

## Sources

- [excid3/ferrum_pdf on GitHub](https://github.com/excid3/ferrum_pdf)
- [Issues](https://github.com/excid3/ferrum_pdf/issues)
- [License: MIT](https://github.com/excid3/ferrum_pdf/blob/main/LICENSE)
- [README](https://github.com/excid3/ferrum_pdf/blob/main/README.md)
- [Releases](https://github.com/excid3/ferrum_pdf/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/excid3-ferrum-pdf
