Project

weasy_pdf

0.0
No release in over 3 years
WeasyPDF generates PDFs from Rails views using WeasyPrint as backend. Near-100% API compatible with Wicked PDF 2.x: same render pdf: options, weasy_pdf_* view helpers (one-command rename from wicked_pdf_*), same railtie auto-include behaviour. Native vite_ruby support. No Chromium. No services. Modern CSS.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

~> 3.1
>= 7.1
~> 13.0
~> 0.22
~> 1.54

Runtime

 Project Readme

WeasyPDF Gem Version Build Status License: MIT Ruby

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 a sed over 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 version

Quick start

class InvoicesController < ApplicationController
  def show
    @invoice = Invoice.find(params[:id])
    render pdf: "invoice_#{@invoice.number}", template: 'invoices/show'
  end
end

No 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:install

Or 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
end

Usage

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:

  1. Vite manifest (if vite_ruby loaded):

    Environment Behavior
    Production CDN <link href="https://cdn.../pdf-abc.css"> — WeasyPrint downloads it
    Dev + Vite server running Inlines CSS via HTTP from localhost:5173
    No Vite server (CI/test) Reads from public/vite/assets/
  2. Disk fallback — searches public/vite/assets/, public/assets/, public/.

  3. base_url fallback — prepends base_url to the asset path.

  4. 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 A0A10 (default: A4)
ISO B B0B10
ISO C C0C10
US Letter, Legal, Ledger
JIS JIS-B0JIS-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 unit

Orientation:

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.rb

3. 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/views

WeasyPrint 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