Migrating from wkhtmltopdf: @page CSS, Fonts & API Guide
You've been using wkhtmltopdf to generate PDFs. Now @page { size: A4; margin: 0; } isn't working as expected, Japanese text shows as tofu boxes, or your CSS Grid layout is completely broken.
These aren't bugs in your code — they're limitations of wkhtmltopdf's aging WebKit engine. And the situation isn't going to improve: wkhtmltopdf's development effectively stopped in 2020.
This guide covers what's different between wkhtmltopdf and modern Chromium-based PDF engines, and shows you exactly how to migrate to FUNBREW PDF with working code in curl, Python, and Node.js.
wkhtmltopdf's Current Status
End of Active Maintenance
wkhtmltopdf's last stable release was 0.12.6, released in 2020. The maintainers have officially indicated that active development has ended. Open issues include:
- No security patches for known vulnerabilities
- No Qt 5 migration completed
- No support for modern CSS features added
- No PDF/A compliance added
Why This Matters for Production
| Risk | Impact |
|---|---|
| No security patches | SSRF and XSS attack vectors remain open |
| Growing CSS gaps | Every UI update risks breaking PDF output |
| OS-level font dependency | Server migrations can silently break Japanese rendering |
| No modern CSS | CSS Grid, Flexbox, custom properties all unavailable |
CSS Compatibility: wkhtmltopdf vs Chromium
Here's what actually breaks when you use modern CSS with wkhtmltopdf.
@page Rule Behavior
The most commonly reported issue:
/* Chromium: works exactly as specified */
/* wkhtmltopdf: margin may be ignored — CLI options take precedence */
@page {
size: A4;
margin: 0;
}
/* Chromium only: first-page overrides */
@page :first {
margin-top: 60mm;
}
/* Chromium only: CSS-based headers and footers */
@page {
@bottom-center {
content: counter(page) " / " counter(pages);
}
}
With wkhtmltopdf, you had to control margins via CLI flags even when @page was present:
# wkhtmltopdf: CSS @page margin is often overridden
wkhtmltopdf \
--margin-top 0 \
--margin-right 0 \
--margin-bottom 0 \
--margin-left 0 \
--page-size A4 \
input.html output.pdf
With Chromium-based engines, CSS @page works exactly as the spec defines:
/* This is all you need */
@page {
size: A4;
margin: 0;
}
Feature Support Comparison
| CSS Feature | wkhtmltopdf | Chromium (FUNBREW PDF) |
|---|---|---|
@page margin |
Partial (CLI overrides) | Full spec support |
@page margin-box (headers/footers) |
Not supported | Supported |
break-inside: avoid |
Partial | Full support |
| CSS Grid | Not supported | Supported |
| Modern Flexbox | Partial | Full support |
| CSS custom properties | Not supported | Supported |
position: sticky |
Not supported | Supported |
| SVG rendering | Unstable | Stable |
| Japanese fonts | OS-dependent (manual setup) | Pre-installed |
Page Break Control
/* wkhtmltopdf: only old properties reliably work */
.keep-together {
page-break-inside: avoid; /* old property */
}
/* Chromium: both old and new properties supported */
.keep-together {
break-inside: avoid; /* modern standard */
page-break-inside: avoid; /* fallback for compatibility */
}
Migration Code: wkhtmltopdf to FUNBREW PDF API
Step 1: Move CLI Margin Options to CSS @page
The first change: replace CLI margin flags with CSS.
/* Replace: --margin-top 10mm --margin-bottom 10mm --margin-left 15mm --margin-right 15mm */
@page {
size: A4;
margin: 10mm 15mm; /* top/bottom: 10mm, left/right: 15mm */
}
Step 2: Replace the subprocess call
curl:
# Before: wkhtmltopdf CLI
# wkhtmltopdf --page-size A4 input.html output.pdf
# After: FUNBREW PDF API
curl -X POST https://pdf.funbrew.cloud/api/pdf/generate \
-H "Authorization: Bearer sk-your-api-key" \
-H "Content-Type: application/json" \
-d '{
"html": "<html>...</html>",
"options": {
"engine": "quality",
"format": "A4"
}
}' \
-o output.pdf
To read from a file and send via curl:
HTML_CONTENT=$(cat input.html)
curl -X POST https://pdf.funbrew.cloud/api/pdf/generate \
-H "Authorization: Bearer sk-your-api-key" \
-H "Content-Type: application/json" \
-d "{
\"html\": $(python3 -c 'import json,sys; print(json.dumps(open("input.html").read()))'),
\"options\": { \"engine\": \"quality\", \"format\": \"A4\" }
}" \
-o output.pdf
Python:
import requests
# Before: wkhtmltopdf via subprocess
# import subprocess
# subprocess.run([
# 'wkhtmltopdf',
# '--page-size', 'A4',
# '--margin-top', '10mm',
# '--encoding', 'utf-8',
# 'input.html', 'output.pdf'
# ])
# After: FUNBREW PDF API
with open('input.html', 'r', encoding='utf-8') as f:
html_content = f.read()
response = requests.post(
'https://pdf.funbrew.cloud/api/pdf/generate',
headers={
'Authorization': 'Bearer sk-your-api-key',
'Content-Type': 'application/json',
},
json={
'html': html_content,
'options': {
'engine': 'quality',
'format': 'A4',
},
},
)
data = response.json()
# Download the generated PDF
pdf_response = requests.get(data['data']['download_url'])
with open('output.pdf', 'wb') as f:
f.write(pdf_response.content)
print('PDF generated:', data['data']['download_url'])
Node.js:
const fs = require('fs');
// Before: wkhtmltopdf via child_process
// const { execSync } = require('child_process');
// execSync(`wkhtmltopdf --page-size A4 input.html output.pdf`);
// After: FUNBREW PDF API
const htmlContent = fs.readFileSync('./input.html', 'utf-8');
const response = await fetch('https://pdf.funbrew.cloud/api/pdf/generate', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk-your-api-key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
html: htmlContent,
options: {
engine: 'quality',
format: 'A4',
},
}),
});
const result = await response.json();
// Download the generated PDF
const pdfResponse = await fetch(result.data.download_url);
const pdfBuffer = await pdfResponse.arrayBuffer();
fs.writeFileSync('./output.pdf', Buffer.from(pdfBuffer));
console.log('PDF generated:', result.data.download_url);
Japanese Font Migration
The wkhtmltopdf Font Problem
With wkhtmltopdf, rendering Japanese text required manual font installation on every Linux server:
# Debian/Ubuntu: required for Japanese to render
sudo apt-get install -y fonts-noto-cjk
# Or install manually from Google's Noto GitHub
If the fonts weren't installed — or if a server was provisioned without them — Japanese text silently broke.
Zero-Configuration Japanese in FUNBREW PDF
FUNBREW PDF has Noto Sans JP pre-installed on every server. No package installation, no Base64 embedding. Just reference the font name in your CSS:
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<style>
body {
/* Noto Sans JP is pre-installed. No @import or <link> needed. */
font-family: 'Noto Sans JP', sans-serif;
font-size: 14px;
line-height: 1.7;
}
h1 { font-weight: 700; }
</style>
</head>
<body>
<h1>日本語タイトル</h1>
<p>ひらがな、カタカナ、漢字、記号(①②③)がすべて正しく表示されます。</p>
</body>
</html>
For air-gapped environments requiring Base64 font embedding, see the Japanese font guide.
Common Post-Migration Issues
Issue 1: Margins Changed
Cause: wkhtmltopdf CLI margin options were not fully reflected in CSS @page. The effective margins differed from the declared CSS values.
Fix: Set @page explicitly with physical units. Use the Playground to visually verify margins:
@page {
size: A4;
margin: 15mm 20mm; /* 15mm top/bottom, 20mm left/right */
}
Issue 2: Page Break Positions Changed
Cause: Chromium applies break-inside: avoid more strictly than wkhtmltopdf did, which can shift where pages break.
Fix: Explicitly declare break behavior for all elements that should stay together:
.invoice-footer,
.data-card,
.section-header {
break-inside: avoid;
page-break-inside: avoid; /* fallback */
}
.new-chapter {
break-before: page;
}
See the CSS tips guide for the full page break reference.
Issue 3: Headers/Footers Behavior Changed
Cause: wkhtmltopdf had --header-html and --footer-html CLI options. These don't translate directly.
Fix (CSS approach): Use @page margin boxes in Chromium:
@page {
margin: 20mm 15mm 25mm 15mm; /* Extra bottom margin for footer */
@bottom-center {
content: "Page " counter(page) " of " counter(pages);
font-size: 9pt;
color: #6b7280;
}
@top-right {
content: "Confidential";
font-size: 8pt;
color: #9ca3af;
}
}
Fix (API approach): Pass headerHtml / footerHtml parameters. See the API docs for details.
Issue 4: Slightly Different Visual Output
Cause: Chromium renders CSS more precisely than wkhtmltopdf. Some CSS hacks that compensated for wkhtmltopdf quirks may produce unexpected results in Chromium.
Fix: Test in the Playground. Paste your HTML and see the exact PDF output in real time. Remove any wkhtmltopdf-specific CSS workarounds.
Migration Checklist
Use this checklist to track your migration:
□ Obtain FUNBREW PDF API key
□ Move CLI margin flags to CSS @page { margin: ...; }
□ Verify HTML encoding is UTF-8
□ Add font-family: 'Noto Sans JP', sans-serif; for Japanese text
□ Replace page-break-inside with break-inside (keep both for compatibility)
□ Test with representative HTML in the Playground
□ Run integration test with production HTML
□ Visually verify: page count, margins, fonts, page breaks
□ Remove wkhtmltopdf subprocess calls from codebase
Summary
| Aspect | wkhtmltopdf | FUNBREW PDF (Chromium) |
|---|---|---|
@page CSS |
Partial — CLI overrides | Full spec support |
margin: 0 |
Often ignored | Works as expected |
| Japanese fonts | Manual OS install required | Pre-installed |
| CSS Grid / modern Flexbox | Not supported | Fully supported |
| Security patches | None (EOL) | Ongoing |
| Headers/footers | CLI --header-html |
CSS @page or API params |
| JavaScript support | Limited | Full Chromium support |
The migration path is straightforward: replace the subprocess call with an API request, move CLI margin flags to CSS @page, and optionally add font-family: 'Noto Sans JP'. Most existing HTML templates work without further changes.
Try your HTML in the Playground to verify the output before updating production code. Full API details are in the API reference.
Related
- wkhtmltopdf vs Chromium Comparison — In-depth engine comparison
- CSS Tips for HTML to PDF — @page, page breaks, margin recipes
- Japanese PDF Font Guide — Base64 embedding and Google Fonts setup
- Migrating from Puppeteer — Similar migration guide for Puppeteer users
- PDF API Production Guide — Performance optimization at scale
- Playground — Test PDF output in real time
- API Reference — Full endpoint documentation