Generate PDF with Ferrum (headless Chrome API)
Tags: rails, ferrum, pdf, htmltopdf
Published: 2024-04-30 12:29:21

---
title: Generate PDF with Ferrum (headless Chrome API)
published: true
date: 2024-01-27 00:00:00 UTC
tags: rubyonrails,ferrum,pdf,htmltopdf
canonical_url: https://blog.corsego.com/gem-ferrum-generate-pdf
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bk8ljl5qx20jiau5pfrh.png
---

Thanks to URLBOX for sponsoring this blogpost! Convert URL to PNG/PDF with [UrlBox screenshot API](https://urlbox.com/screenshot-api). Urlbox also helps to block Ads & Popups, Bypass Captchas, Auto-Accept Cookies.

****

Usually I would use an HTML-to-PDF library to to generate and display a PDF.

**An absolutely different** , alternative way to view or download a page as PDF would be via a **headless browser API**.

[Gem Ferrum](https://github.com/rubycdp/ferrum) lets you open a headless chrome browser and perform different actions (click link, take screenshot, open as PDF, etc.)

Why not use Ferrum to open or save a file as PDF?

Here’s how it could work:

Example of an HTML page ⤵️

![Ferrum HTML page](https://blog.corsego.com/assets/images/ferrum-page-html.png)

Clicking the _“View as PDF”_ link would open a link as PDF ⤵️

![Ferrum page turned into PDF](https://blog.corsego.com/assets/images/ferrum-page-pdf.png)

### Install and use [Gem Ferrum](https://github.com/rubycdp/ferrum)

Install the gem:

```
# Gemfile
gem "ferrum"

```

Add a route:

```
# config/routes.rb
get 'home/index'

```

Add an HTML page.

You can add a `link_to` render the page as PDF.

Notice I add a class `.no-print`, so that the link does not get displayed in `print` media type.

```
<!-- app/views/home/index.html.erb -->
<h1>Home#index</h1>
<p>Find me in app/views/home/index.html.erb</p>
<div class="no-print">
  <%= link_to "View as PDF", home_index_path(format: :pdf) %>
</div>

```

Add `.no-print ` CSS class to elements that should not be printable:

```
/* app/assets/stylesheets/application.css */
@media print {
  .no-print {
    display: none !important;
  }
}

/* other css classes you might want to add */
@media print {
  body {
    -webkit-print-color-adjust: exact;
    background: #fff;
    background-color: #fff;
    float: none;
    display: block;
  }
  .no_margin {
    padding-left: 0px !important;
  }
  .no-print {
    display: none !important;
  }
  .printer-preview-content,
  .printer-preview-content_landscape {
    max-width: 100% !important;
    width: 100% !important;
    height: auto !important;
    padding: 0 !important;
    margin: 0 !important;
  }
  .cover_div {
    display: table;
  }
}

```

Finally, use Ferrum to navigate to an internal or external URL and display it as PDF:

```
# app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
    respond_to do |format|
      format.html
      format.pdf do
        handle_pdf_format
      end
    end
  end

  private

  def handle_pdf_format
    filename = controller_name
    tmp = Tempfile.new("pdf-chrome-#{filename}")
    browser = Ferrum::Browser.new
    # browser.go_to("http://localhost:3000/home/index")
    browser.go_to(home_index_url)
    # browser.go_to("https://google.com")
    # click_on_text "Accept all"
    # browser.at_css(".QS5gu.sy4vM").click
    sleep(0.3)
    browser.pdf(
      path: tmp.path,
      format: "A4".to_sym,
      landscape: false,
      # margin: {top: 36, right: 36, bottom: 36, left: 36},
      # preferCSSPageSize: true,
      # printBackground: true
    )
    browser.quit
    pdf_data = File.read(tmp.path)
    pdf_filename = "#{filename}.pdf"
    send_data(pdf_data,
              filename: pdf_filename,
              type: "application/pdf",
              disposition: "inline")
  ensure
    tmp.close
    tmp.unlink
  end
end

```

It works well for publicly accessible URLs, however it is not so straightforwar for links that require `current_user` authentication. I’ve asked a question here [https://github.com/rubycdp/ferrum/issues/423](https://github.com/rubycdp/ferrum/issues/423) and am hoping for an easy enough solution 🤠