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 and WickedPDF. They both use a command line utility called wkhtmltopdf
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 or grid; 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. It uses Puppeteer and Chromium 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
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!
def render_pdf(html, filename:)
pdf = Grover.new(html, format: 'A4').to_pdf
send_data pdf, filename: filename, type: "application/pdf"
end
I put this method in a Concern 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:
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
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.
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
Top comments (5)
With wkhtmltopdf not maintained anymore, I think Puppeteer is the best way to do HTML to PDF. But beware it can be tricky to scale your pdf exports
Hey there! Thank you for this article, it was really useful since there isn't much on the internet about implementing Grover.
I was wondering if you might have any idea why the server isn't outputting a file. It's not raising any exceptions, but it doesn't recognise the
format.pdf
bit.Any advice is helpful. Thank you again!
Hey ... sorry I can't think of any likely issue off the top of my head I'm afraid ... it could literally be anything :(.
If you can email me a link to a GitHub repo with the issue I'll try to take a peek when I get some time, but might be a week or two before I get round to looking at it.
ayush (at) radioactivetoy (dot) tech.
Yeah, I figured... I'm in way over my head anyway, as a junior this is a bit too much for me haha
Sadly the code isn't mine to share, since it belongs to the company I work for. My senior colleague will be taking over, hopefully he'll be able to make it work.
Thank you so much for the offer anyway. Take care!
Hey, thanks for sharing. It's nice to know there are other alternatives to WickedPDF. Yeah, sadly support for modern CSS in WKHTMLTOPDF is very poor but it's still useful for simple styling.