Converting HTML to PDF using Rails
Tags: ruby, rails
Published: 2021-06-07 10:16:54

Exporting to PDF from HTML can be a bit of a can of worms, especially with CSS not quite working the way it does in a web browser. However with the right setup, it's possible to take the pain out of it!   
  
A couple of popular gems to convert HTML to PDF in Rails are [PDFKit](https://github.com/pdfkit/pdfkit) and [WickedPDF](https://github.com/mileszs/wicked_pdf). They both use a command line utility called [`wkhtmltopdf`](https://wkhtmltopdf.org) under the hood; which uses WebKit to render a PDF from HTML.   
  
I'd highly advise against using both those gems. They're good libraries but the underlying `wkhtmltopdf` doesn't support modern CSS features such as [custom properties](https://developer.mozilla.org/en-US/docs/Web/CSS/--*) or [grid](https://www.w3schools.com/css/css_grid.asp); so you might find yourself unable to use any of the existing CSS in your app to style your PDF export.  
  
The gem I recommend is called [Grover](https://github.com/Studiosity/grover). It uses [Puppeteer](https://github.com/puppeteer/puppeteer) and [Chromium](https://www.chromium.org/Home) to "print" an HTML page into a PDF. So your PDF will look exactly how your page looks in Google Chrome's print preview. This will also allow you to reuse CSS from your app rather than having to write specific CSS just for your PDF exports.  
  
Since Grover uses Chromium which runs external of your Rails app, you need to reference all your assets with absolute paths instead of relative paths. The easiest way to enable this is to set `config.asset_host` in your app configuration. This ensures the `stylesheet_link_tag`&nbsp; and `font-url` helpers output the absolute path including your domain name rather than just the relative path.  
  
Depending on the complexity of your requirements, you might want to set up Grover as a middleware. You can read up on how to do that in their comprehensive Readme. However if all you're trying to do is allow a user to download a dynamically generated PDF, the below controller code is all that's needed!  
  
  

```ruby
def render_pdf(html, filename:)
  pdf = Grover.new(html, format: 'A4').to_pdf
  send_data pdf, filename: filename, type: "application/pdf"
end
```

&nbsp;  
I put this method in a [Concern](https://api.rubyonrails.org/classes/ActiveSupport/Concern.html) so I can include it in any controller I need to. I'd also recommend creating a new layout for your PDFs as you likely won't need all the markup your `application.html.erb` includes.  
  
Here's an example of a controller action that generates and triggers a download of a PDF for an invoice:  
  
&nbsp;

```ruby
def download
  invoice = render_to_string "download.html.erb", layout: "pdf"

  respond_to do |format|
    format.html { render html: invoice }
    format.pdf { render_pdf invoice, filename: t(".filename", id: @invoice.id) }
  end
end
```

&nbsp;  
Having both HTML and PDF formats for this action enables a quick feedback loop during development. It allows you to view the HTML page in your browser and then test the PDF export once you have the basics down.&nbsp;  
  
Exporting to PDF doesn't have to be a pain thanks to Grover! You might still find some quirks with your CSS so you might have to create a separate CSS bundle for PDFs; however the vast majority of your CSS should "just work".

*This post was originally published on my [blog](https://binarysolo.chapter24.blog/converting-html-to-pdf-using-rails/)*