Why generate PDFs from HTML?
HTML and CSS are the best layout engines we have. Instead of wrestling with PDF libraries that have their own rendering quirks, you can design your document in HTML — using flexbox, grid, web fonts, and real CSS — then convert it to PDF.
Puppeteer
Puppeteer launches a headless Chromium browser, navigates to a URL, and calls page.pdf() to generate a PDF. It produces accurate output because it uses the same engine as Chrome.
#E8A0BF">const puppeteer = require('puppeteer');
#E8A0BF">async function generatePdf(url) {
#E8A0BF">const browser = await puppeteer.launch();
#E8A0BF">const page = await browser.newPage();
#E8A0BF">await page.goto(url, { waitUntil: 'networkidle0' });
#E8A0BF">await page.pdf({
path: #A8D4A0">'output.pdf',
format: #A8D4A0">'A4',
printBackground: #E8A0BF">true,
margin: { top: #A8D4A0">'1in', bottom: '1in' }
});
#E8A0BF">await browser.close();
}
generatePdf(#A8D4A0">'https://example.com/invoice/1234');Production issues
- !500 MB+ RAM per browser instance — expensive at scale
- !Chromium crashes under load with no built-in recovery
- !No request queue — concurrent PDF jobs compete for memory
- !Cold starts add 2-5 seconds per generation
- !Chromium binary must be installed in your deployment environment
wkhtmltopdf
A command-line tool that uses an old WebKit engine to render HTML to PDF. It was the go-to solution for years, but the project is now deprecated and no longer maintained.
# Install (Debian/Ubuntu)
sudo apt-get install wkhtmltopdf
# Convert a URL to PDF
wkhtmltopdf https://example.com/invoice/1234 output.#87CEEB">pdf
# With options
wkhtmltopdf #FFB347">--page-size A4 \
#FFB347">--margin-top 25mm \
#FFB347">--margin-bottom 25mm \
#FFB347">--enable-local-file-access \
https://example.com/invoice/1234 output.#87CEEB">pdfWhy you should avoid it
- !Officially deprecated — no security patches or updates
- !Uses an ancient WebKit fork that chokes on flexbox, grid, and modern CSS
- !No JavaScript execution — dynamic content renders blank
- !Web fonts often fail to load or render incorrectly
- !Difficult to install in containerized environments
SnapRender API (recommended)
Send a URL or raw HTML to /v1/pdf and get back a PDF. No browser to install, no memory to manage, no crashes to handle. Five lines of code.
Convert a URL to PDF
#E8A0BF">const resp = await fetch("https://api.snaprender.dev/v1/pdf", {
method: #A8D4A0">"POST",
headers: {
#A8D4A0">"x-api-key": "sr_live_YOUR_KEY",
#A8D4A0">"Content-Type": "application/json",
},
body: JSON.#87CEEB">stringify({
url: #A8D4A0">"https://example.com/invoice/1234",
format: #A8D4A0">"A4",
print_background: #E8A0BF">true,
}),
});
#E8A0BF">const buffer = await resp.arrayBuffer();
fs.#87CEEB">writeFileSync("invoice.pdf", Buffer.from(buffer));Also available in cURL and Python
No SDK required — it is a standard REST API.
curl #FFB347">-X POST https://api.snaprender.dev/v1/pdf \
#FFB347">-H "x-api-key: sr_live_YOUR_KEY" \
#FFB347">-H "Content-Type: application/json" \
#FFB347">-d '{
#A8D4A0">"url": "https://example.com/invoice",
#A8D4A0">"format": "A4",
#A8D4A0">"print_background": true
}' #FFB347">--output invoice.pdfAdvanced: Convert raw HTML to PDF
You do not need to host a page to generate a PDF. Pass your HTML string directly via the html parameter. SnapRender renders it in a real Chromium instance — CSS, web fonts, and images all work. Here is a complete invoice template:
#E8A0BF">const invoiceHtml = `
<html>
<head>
<style>
body { font-family: #A8D4A0">'Helvetica Neue', sans-serif; color: #1a1a1a; padding: 48px; }
.header { display: flex; justify-content: space-between; margin-bottom: 48px; }
.company { font-size: 24px; font-weight: 700; }
.invoice-num { color: #666; font-size: 14px; }
table { width: 100%; border-collapse: collapse; margin-top: 32px; }
th { text-align: left; padding: 12px 0; border-bottom: 2px solid #1a1a1a; font-size: 13px; text-transform: uppercase; letter-spacing: 0.05em; }
td { padding: 12px 0; border-bottom: 1px solid #e5e5e5; font-size: 14px; }
.total-row td { border-bottom: none; font-weight: 700; font-size: 16px; padding-top: 16px; }
.amount { text-align: right; }
.footer { margin-top: 64px; font-size: 12px; color: #999; }
</style>
</head>
<body>
<div class=#A8D4A0">"header">
<div>
<div class=#A8D4A0">"company">Acme Corp</div>
<div class=#A8D4A0">"invoice-num">Invoice #INV-2026-0042</div>
</div>
<div style=#A8D4A0">"text-align: right; font-size: 14px; color: #666;">
<div>April 12, 2026</div>
<div>Due: May 12, 2026</div>
</div>
</div>
<table>
<thead>
<tr><th>Description</th><th class=#A8D4A0">"amount">Amount</th></tr>
</thead>
<tbody>
<tr><td>Web Development - April</td><td class=#A8D4A0">"amount">$4,500.00</td></tr>
<tr><td>Hosting & Infrastructure</td><td class=#A8D4A0">"amount">$120.00</td></tr>
<tr><td>SSL Certificate (annual)</td><td class=#A8D4A0">"amount">$79.00</td></tr>
<tr class=#A8D4A0">"total-row"><td>Total</td><td class="amount">$4,699.00</td></tr>
</tbody>
</table>
<div class=#A8D4A0">"footer">Payment terms: Net 30. Please include invoice number with payment.</div>
</body>
</html>
`;
#E8A0BF">const resp = await fetch("https://api.snaprender.dev/v1/pdf", {
method: #A8D4A0">"POST",
headers: {
#A8D4A0">"x-api-key": "sr_live_YOUR_KEY",
#A8D4A0">"Content-Type": "application/json",
},
body: JSON.#87CEEB">stringify({
html: invoiceHtml,
format: #A8D4A0">"A4",
print_background: #E8A0BF">true,
margin: { top: #A8D4A0">"0.5in", bottom: "0.5in", left: "0.5in", right: "0.5in" },
}),
});
#E8A0BF">const buffer = await resp.arrayBuffer();
fs.#87CEEB">writeFileSync("invoice.pdf", Buffer.from(buffer));Customization options
The /v1/pdf endpoint accepts these parameters to control output:
| Parameter | Type | Description |
|---|---|---|
url | string | URL of the page to convert |
html | string | Raw HTML string (alternative to url) |
format | string | Page size: A4, Letter, Legal, Tabloid |
width / height | string | Custom dimensions (e.g. "8.5in", "210mm") |
margin | object | { top, right, bottom, left } in inches or mm |
print_background | boolean | Include CSS backgrounds and colors |
landscape | boolean | Landscape orientation |
Side-by-side comparison
| Puppeteer | wkhtmltopdf | SnapRender | |
|---|---|---|---|
| Modern CSS/JS | Yes | No | Yes |
| RAM per request | 500 MB+ | ~100 MB | 0 (API) |
| Setup complexity | Medium | Low | None |
| Production stability | Fragile | Deprecated | Managed |
| Raw HTML input | Yes | Yes | Yes |
| Custom page sizes | Yes | Limited | Yes |
| Maintained | Yes | No | Yes |
| Cost | Server RAM | Free | From $0 |
Generate PDFs for free — 100/month
No credit card. No headless browser. Just an API key and five lines of code.
Frequently asked questions
For production use, an API like SnapRender is the most reliable approach. You send a URL or raw HTML and get back a PDF — no headless browser to manage, no memory issues, and no crashes at scale. For local prototyping, Puppeteer works but becomes a liability in production.
Yes. SnapRender accepts an html parameter with your full HTML string, including inline CSS and images. The API renders it in a real browser engine and returns a pixel-perfect PDF. No need to deploy a page first.
Pass format (A4, Letter, Legal, Tabloid) and margin (top, right, bottom, left in inches or millimeters) in the request body. You can also set custom width and height for non-standard sizes like receipts or labels.
SnapRender includes 100 free PDF generations per month with no credit card required. Paid plans start at $9/month for higher volumes with the same flat per-request pricing.