PDF Generation Guide: HTML to PDF in Node.js, Python, Go, and PHP
A practical comparison of every HTML-to-PDF approach: wkhtmltopdf, Puppeteer, WeasyPrint, and managed APIs. Code examples for every major backend language.
Why PDF Generation is Still Hard
Despite being a solved problem in theory, PDF generation remains one of the most frustrating tasks in backend development. The challenge is CSS rendering fidelity: modern web pages use flexbox, grid, CSS custom properties, web fonts, SVG, and JavaScript-rendered content that most PDF libraries simply cannot handle correctly. The result is PDFs that look nothing like the HTML template you designed, with broken layouts, missing fonts, and clipped content.
This guide covers every major approach and when to use each, with working code examples in Node.js, Python, Go, and PHP.
Option 1: wkhtmltopdf
wkhtmltopdf uses a patched WebKit engine to render HTML and convert it to PDF. It was the standard solution for a decade but has serious limitations: the WebKit version is old, flexbox support is partial, CSS Grid is not supported, and it requires system libraries to be installed on every server. On macOS and modern Linux distros, installation involves multiple workarounds. Maintenance of the project has effectively stopped. Not recommended for new projects.
Option 2: Puppeteer or Playwright
Puppeteer and Playwright use a real Chromium browser, giving you modern CSS support and JavaScript execution. They are the most accurate option for HTML-to-PDF conversion and are suitable when you control the server infrastructure. The downsides: the Chromium binary is 150-400 MB, process management is complex, and memory usage per concurrent job is significant.
// Node.js with Playwright
const { chromium } = require("playwright");
async function htmlToPdf(url) {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(url, { waitUntil: "networkidle" });
const pdf = await page.pdf({ format: "A4", printBackground: true });
await browser.close();
return pdf;
}
This works well for low-to-medium concurrency. At high concurrency, you need a browser pool to limit open browser instances and prevent memory exhaustion.
Option 3: WeasyPrint (Python)
WeasyPrint is a Python library that converts HTML and CSS to PDF without a browser. It has good CSS support for print-specific features like @page rules and page-break-* properties. It does not execute JavaScript, so it works best for server-rendered HTML templates without dynamic content. Installation requires system dependencies (Cairo, Pango) that can be tricky on minimal Docker images.
Option 4: Managed PDF API
A managed API like SnapAPI handles Chromium PDF rendering remotely. Your backend makes one HTTP call, receives PDF bytes, and delivers them. No binary installation, no process management, no memory overhead, no Chromium version pinning. Works on shared hosting, serverless, edge functions, and any environment that can make outbound HTTPS requests.
# Python — identical pattern works in Node.js, Go, PHP, Ruby
import requests
resp = requests.get(
"https://api.snapapi.pics/v1/pdf",
params={"url": "https://your-app.com/invoice/123", "format": "A4", "print_background": "true"},
headers={"X-Api-Key": "YOUR_API_KEY"},
timeout=60
)
with open("invoice.pdf", "wb") as f:
f.write(resp.content)
This is the right choice when: you are on serverless or edge infrastructure, you do not want to manage Chromium, you need to support multiple languages, or you want to start quickly without infrastructure setup.
Choosing the Right Approach
Use Puppeteer or Playwright if: you already run Node.js with a Chromium binary for other purposes, you need maximum control, and you have ops capacity to manage browser processes. Use WeasyPrint if: you are Python-only, your templates are fully server-rendered, and you need print-specific CSS features. Use a managed API like SnapAPI if: you want zero infrastructure overhead, you need cross-language support, or you are on serverless.
Sign up at snapapi.pics for 200 free PDF generations per month. No credit card required. Your first PDF renders in under five minutes from any language.