Generate PDFs from Rails views using WeasyPrint — modern CSS, no JavaScript, no Chromium.
gem 'weasy_pdf'Migrating from
wicked_pdf? Jump to Migration — typically a one-line Gemfile change plus asedover your views.
Requirements
- Ruby >= 3.2
- Rails >= 7.1
- WeasyPrint installed on the system:
apt install weasyprint # Debian/Ubuntu
brew install weasyprint # macOS
pip install weasyprint # latest versionQuick start
class InvoicesController < ApplicationController
def show
@invoice = Invoice.find(params[:id])
render pdf: "invoice_#{@invoice.number}", template: 'invoices/show'
end
endNo initializer, no ApplicationController changes — the Railtie wires everything automatically.
Configuration
Optional — sensible defaults are used if you skip this. The fastest way to scaffold an initializer with all options documented:
bin/rails generate weasy_pdf:installOr write it yourself:
# config/initializers/weasy_pdf.rb
WeasyPDF.configure do |config|
config.default_options = {
page_size: 'A4',
margin_top: '15mm',
media_type: 'print',
}
# base_url prepends to relative URLs in the rendered HTML when no asset
# can be resolved from disk. Useful in development.
config.base_url = Rails.env.development? ? 'http://localhost:3000' : nil
config.timeout = 60
endUsage
Controllers
render pdf: 'invoice',
template: 'invoices/show',
layout: 'pdf',
page_size: 'A4',
margin: { top: 15, bottom: 15 },
header: { right: '[page] of [topage]', font_size: 9 },
show_as_html: params.key?('debug')Jobs / Mailers
pdf = render_to_string pdf: 'invoice', template: 'invoices/show', layout: 'pdf'Direct instantiation
WeasyPDF.new.pdf_from_string('<h1>Hello</h1>')
WeasyPDF.new.pdf_from_html_file('/path/to/file.html')
WeasyPDF.new.pdf_from_url('https://example.com')PDF layout
<!DOCTYPE html>
<html>
<head>
<%= weasy_pdf_stylesheet_link_tag 'pdf' %>
</head>
<body>
<%= weasy_pdf_image_tag 'logo.png', width: 150 %>
<%= yield %>
</body>
</html>Asset helpers
<%= weasy_pdf_stylesheet_link_tag 'pdf' %>
<%= weasy_pdf_image_tag 'logo.png', width: 150 %>
<%= weasy_pdf_asset_path 'logo.png' %>
<%= weasy_pdf_asset_base64 'logo.png' %>
<%= weasy_pdf_url_base64 'https://example.com/img.png' %>Asset support
Vite-first (auto-detected when vite_ruby is present) with fallback to precompiled assets from public/.
Each asset is resolved in this order:
-
Vite manifest (if
vite_rubyloaded):Environment Behavior Production CDN <link href="https://cdn.../pdf-abc.css">— WeasyPrint downloads itDev + Vite server running Inlines CSS via HTTP from localhost:5173 No Vite server (CI/test) Reads from public/vite/assets/ -
Disk fallback — searches
public/vite/assets/,public/assets/,public/. -
base_urlfallback — prependsbase_urlto the asset path. -
Returns input unchanged — logs a warning so misconfigurations are visible.
Headers and footers
CSS @page margin boxes — text and counters only:
render pdf: 'report',
header: { left: 'Acme', center: 'Confidential', right: '[page] of [topage]' },
footer: { center: '[page]', font_size: 8 }| Token | CSS output |
|---|---|
[page] |
counter(page) |
[topage] |
counter(pages) |
[section], [title], [date], [time], [webpage]
|
Removed — no CSS equivalent |
For complex headers (logos, styled HTML), use WeasyPrint running elements:
.pdf-header { position: running(header); }
@page { @top-center { content: element(header); } }<div class="pdf-header">
<img src="<%= weasy_pdf_asset_path('logo.png') %>" height="30">
<span>Invoice #<%= @invoice.number %></span>
</div>Page sizes and orientation
page_size: accepts any CSS Paged Media keyword:
| Family | Sizes |
|---|---|
| ISO A |
A0 … A10 (default: A4) |
| ISO B |
B0 … B10
|
| ISO C |
C0 … C10
|
| US |
Letter, Legal, Ledger
|
| JIS |
JIS-B0 … JIS-B5
|
Custom dimensions (numbers default to mm; strings pass through):
render pdf: 'receipt', page_width: 80, page_height: 200 # 80mm × 200mm
render pdf: 'badge', page_width: '4in', page_height: '6in' # any CSS unitOrientation:
render pdf: 'report', page_size: 'A4', orientation: 'Landscape'page_width / page_height take precedence over page_size / orientation.
Rack Middleware
# config/application.rb
config.middleware.use WeasyPDF::Middleware
config.middleware.use WeasyPDF::Middleware, { page_size: 'A4' }, only: [/^\/invoices/]Requests to /invoices/1.pdf are transparently rendered as PDF.
Limitations
| JavaScript execution | Not supported — WeasyPrint is JS-free. Render server-side first. |
| HTML template headers/footers | Not supported — raises WeasyPDF::Error pointing to running elements |
Webpacker / Shakapacker *_pack_tag helpers |
Not supported — no equivalents |
pdf_from_url |
Basic HTTP only — no auth, no JS rendering |
outline, dpi, proxy, cookie, extra
|
Accepted silently (migration compat), not passed to WeasyPrint |
Migration from Wicked PDF
Not migrating from
wicked_pdf? You can stop reading here.
Why migrate
| wicked_pdf | weasy_pdf | |
|---|---|---|
| Backend | wkhtmltopdf (archived 2023) | WeasyPrint (active) |
| CSS engine | WebKit 2013 — no flexbox/grid | Modern CSS |
| RAM per worker | ~75–200MB + known leaks | ~35–80MB, clean exit |
| Vite | Manual workarounds | Native (auto-detected) |
| Runtime deps |
activesupport, ostruct
|
activesupport |
Steps
1. Gemfile
- gem 'wicked_pdf'
- gem 'wkhtmltopdf-binary'
+ gem 'weasy_pdf'2. Initializer
mv config/initializers/wicked_pdf.rb config/initializers/weasy_pdf.rb
sed -i 's/WickedPdf/WeasyPDF/g' config/initializers/weasy_pdf.rb3. Bulk-rename helpers in views
find app/views -name '*.erb' | xargs sed -i 's/wicked_pdf_/weasy_pdf_/g'4. Delete dead helper calls
After step 3 your views may contain weasy_pdf_javascript_* or weasy_pdf_*_pack_tag calls
that don't exist in weasy_pdf and would raise NoMethodError at render time:
grep -rE 'weasy_pdf_(javascript_|.*_pack_)' app/viewsWeasyPrint doesn't execute JavaScript and Webpacker isn't supported — removing them is correct.
5. HTML template headers
header: { html: { template: ... } } now raises WeasyPDF::Error with a migration hint.
Replace with running elements.
6. Controllers — no changes
render pdf:, render_to_string pdf:, all PDF options work identically.
7. Dockerfile
RUN apt-get update && apt-get install -y --no-install-recommends \
weasyprint fonts-dejavu-core fonts-liberation \
&& rm -rf /var/lib/apt/lists/*Performance differences
Neither backend is universally faster — pick by workload:
- Long-running Sidekiq workers → weasy_pdf (no RAM leaks)
- High-concurrency web requests → weasy_pdf (lower per-process RAM)
- Documents using flexbox / grid / modern CSS → weasy_pdf (wkhtmltopdf can't render them)
- One-shot CLI on simple HTML → roughly comparable, document complexity dominates
License
MIT