May 9, 2026

HTML to PDF in 2026: All Methods Compared with Code

HTML to PDFcomparisonwkhtmltopdfPuppeteerPDF API

Converting HTML to PDF is a solved problem — in the sense that many tools exist. Choosing the right one for your use case is not trivial, because each method makes different trade-offs on rendering fidelity, CSS support, operational complexity, and cost.

This article covers every major HTML to PDF method available in 2026, with honest pros, cons, and real code for each approach.

For a focused comparison of Puppeteer specifically against a managed API, see Puppeteer vs PDF API. For CSS tips that apply across all Chromium-based methods, see HTML to PDF CSS Tips.

The Methods at a Glance

Method Engine CSS Support Operational Complexity Self-Hosted
Browser print dialog Chromium / WebKit Full None (manual)
wkhtmltopdf QtWebKit (archived) Partial Low Yes
Puppeteer / Playwright Chromium Full High Yes
WeasyPrint Python rendering Good Low–Medium Yes
Gotenberg Chromium + LibreOffice Full Medium Yes
Managed PDF API Chromium Full Very Low No

1. Browser Print Dialog

The simplest method: window.print() in JavaScript opens the browser's native print dialog, letting the user save as PDF.

// Trigger print dialog
window.print();

// Or add a print button
document.getElementById('print-btn').addEventListener('click', () => {
  window.print();
});

Pair with CSS @media print rules to control layout:

@media print {
  .no-print { display: none; }
  body { font-size: 12pt; }
  @page { margin: 20mm; size: A4; }
}

Pros:

  • Zero setup, zero cost
  • Uses the full browser rendering engine
  • No server-side code required

Cons:

  • User must manually save as PDF — not automatable
  • Print dialog appearance varies by browser and OS
  • Page breaks are hard to control reliably
  • Headers, footers, and multi-page layout are difficult
  • Cannot be triggered silently from a server

Best for: Developer tools, internal dashboards where a "save as PDF" button is sufficient and automation is not needed.

2. wkhtmltopdf

A command-line tool based on the QtWebKit rendering engine. It was the dominant solution for server-side HTML to PDF conversion for many years.

# Install (Debian/Ubuntu)
sudo apt-get install wkhtmltopdf

# Basic conversion
wkhtmltopdf input.html output.pdf

# With options
wkhtmltopdf \
  --page-size A4 \
  --margin-top 20mm \
  --margin-bottom 20mm \
  --margin-left 15mm \
  --margin-right 15mm \
  --encoding utf-8 \
  invoice.html invoice.pdf

# From a URL
wkhtmltopdf https://example.com page.pdf
# Python wrapper
import subprocess

def html_to_pdf(html_path: str, output_path: str) -> None:
    subprocess.run([
        'wkhtmltopdf',
        '--page-size', 'A4',
        '--margin-top', '20mm',
        '--margin-bottom', '20mm',
        html_path,
        output_path,
    ], check=True)

Pros:

  • Lightweight and fast (lower resource usage than Chromium-based tools)
  • Simple CLI interface
  • Mature and widely documented

Cons:

  • Archived in 2023 — no security patches or bug fixes
  • QtWebKit engine is 7–8 years behind modern browsers
  • Incomplete CSS Grid and Flexbox support
  • Cannot render pages that require JavaScript execution
  • CJK font support requires manual font installation
  • Not recommended for new projects

Best for: Legacy systems already using wkhtmltopdf that are not yet migrated. For migration guidance, see the wkhtmltopdf migration guide.

3. Puppeteer (Node.js)

Puppeteer is a Node.js library that controls a headless Chromium instance. It was originally built for browser automation and testing; PDF generation is one of its features.

const puppeteer = require('puppeteer');

async function generatePdf(html) {
  const browser = await puppeteer.launch({
    headless: 'new',
    args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'],
  });

  const page = await browser.newPage();
  await page.setContent(html, { waitUntil: 'networkidle0' });

  const pdf = await page.pdf({
    format: 'A4',
    margin: { top: '20mm', bottom: '20mm', left: '15mm', right: '15mm' },
    printBackground: true,
  });

  await browser.close();
  return pdf;
}

Pros:

  • Full modern CSS support (Grid, Flexbox, custom properties)
  • Can run JavaScript before generating the PDF
  • Large community and ecosystem
  • Identical rendering to Chrome

Cons:

  • Requires Chromium installation and version management
  • 200–500 MB memory per process
  • Cold starts: 1–3 seconds (8–15 seconds on Lambda)
  • Browser pool required for concurrent use
  • Docker images balloon to 1 GB+
  • Not purpose-built for PDF generation

Best for: Low-to-medium volume PDF generation where you are already using Puppeteer for browser testing, or where you need page.evaluate() to run JavaScript before export.

4. Playwright (Node.js, Python, .NET, Java)

Playwright is Microsoft's browser automation library. It supports Chromium, Firefox, and WebKit, and offers a cleaner API than Puppeteer. For PDF generation, it uses Chromium, so the output is identical.

// Node.js
const { chromium } = require('playwright');

async function generatePdf(html) {
  const browser = await chromium.launch();
  const context = await browser.newContext();
  const page = await context.newPage();

  await page.setContent(html, { waitUntil: 'networkidle' });
  const pdf = await page.pdf({
    format: 'A4',
    margin: { top: '20mm', bottom: '20mm', left: '15mm', right: '15mm' },
    printBackground: true,
  });

  await browser.close();
  return pdf;
}
# Python
import asyncio
from playwright.async_api import async_playwright

async def generate_pdf(html: str) -> bytes:
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        page = await browser.new_page()
        await page.set_content(html, wait_until='networkidle')
        pdf = await page.pdf(
            format='A4',
            margin={'top': '20mm', 'bottom': '20mm',
                    'left': '15mm', 'right': '15mm'},
            print_background=True,
        )
        await browser.close()
        return pdf

Pros:

  • Cleaner API than Puppeteer
  • Multi-language support (Node.js, Python, .NET, Java)
  • Active development and maintenance
  • Full modern CSS support

Cons:

  • Same core problems as Puppeteer: Chromium management, 200–500 MB memory, cold starts
  • Playwright installs its own Chromium bundle (playwright install chromium) — even larger download than Puppeteer
  • Not purpose-built for PDF generation

Best for: Same situations as Puppeteer, when you prefer Python or .NET, or when you need Firefox/WebKit rendering for testing. If operational problems are the pain point, migrating to a managed API is the higher-ROI move. See the Playwright to PDF API migration guide.

5. WeasyPrint

WeasyPrint is a Python library that converts HTML and CSS to PDF using its own rendering engine (not Chromium). It implements the CSS Paged Media specification directly.

# Install
pip install weasyprint

# CLI
weasyprint input.html output.pdf
from weasyprint import HTML, CSS

def generate_pdf(html: str, css: str = None) -> bytes:
    html_doc = HTML(string=html)
    stylesheets = [CSS(string=css)] if css else []
    return html_doc.write_pdf(stylesheets=stylesheets)

WeasyPrint has excellent support for CSS Paged Media features like @page rules, named pages, and running elements:

@page {
  size: A4;
  margin: 20mm;

  @bottom-center {
    content: "Page " counter(page) " of " counter(pages);
  }
}

/* Running header/footer */
.header { position: running(header); }
@page { @top-center { content: element(header); } }

Pros:

  • Excellent CSS Paged Media support (headers, footers, page counters)
  • Lightweight — no Chromium required
  • Pure Python — easy to integrate into Django, Flask, FastAPI
  • Good for documents with complex multi-page layout

Cons:

  • Does not support JavaScript execution
  • CSS support is not identical to browsers (some modern properties missing)
  • CJK font support requires font installation
  • Slower than Chromium for complex pages with many CSS rules

Best for: Python applications generating structured documents (reports, books, contracts) where CSS Paged Media features are important and JavaScript rendering is not needed.

6. Gotenberg

Gotenberg is an open-source Docker service that wraps Chromium and LibreOffice to expose a REST API for document conversion.

# Start Gotenberg with Docker
docker run --rm -p 3000:3000 gotenberg/gotenberg:8

# Convert HTML to PDF
curl \
  --request POST http://localhost:3000/forms/chromium/convert/html \
  --form files=@index.html \
  --form marginTop=20 \
  --form marginBottom=20 \
  -o output.pdf

# Convert a URL
curl \
  --request POST http://localhost:3000/forms/chromium/convert/url \
  --form url=https://example.com \
  -o output.pdf
import requests

def generate_pdf_gotenberg(html: str) -> bytes:
    response = requests.post(
        'http://localhost:3000/forms/chromium/convert/html',
        files={'files': ('index.html', html, 'text/html')},
        data={'marginTop': '20', 'marginBottom': '20'},
    )
    response.raise_for_status()
    return response.content

Pros:

  • Language-agnostic REST API
  • Full Chromium rendering capabilities
  • Also converts Word, Excel, and PowerPoint files (via LibreOffice)
  • One-command Docker setup for local development

Cons:

  • Requires Docker — difficult to run in serverless environments
  • Self-managed scaling (you run and scale the container)
  • Large Docker image (Chromium + LibreOffice)
  • CJK font support requires additional Docker configuration
  • Limited built-in error handling and retry support

Best for: Teams that want a self-hosted REST API with language-agnostic access, and who also need Office document conversion. Kubernetes deployments where you manage scaling. Not suited to serverless.

7. Managed PDF API

A managed PDF API (such as FUNBREW PDF) is a hosted service. You POST HTML and receive PDF bytes. No server infrastructure to manage.

// Node.js
async function generatePdf(html) {
  const response = await fetch('https://pdf.funbrew.cloud/api/v1/pdf/generate', {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.FUNBREW_PDF_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      html,
      options: {
        format: 'A4',
        margin: { top: '20mm', bottom: '20mm', left: '15mm', right: '15mm' },
        print_background: true,
      },
    }),
  });
  if (!response.ok) throw new Error(`API error: ${response.status}`);
  return Buffer.from(await response.arrayBuffer());
}
# Python
import os, requests

def generate_pdf(html: str) -> bytes:
    resp = requests.post(
        'https://pdf.funbrew.cloud/api/v1/pdf/generate',
        headers={
            'X-API-Key': os.environ['FUNBREW_PDF_API_KEY'],
            'Content-Type': 'application/json',
        },
        json={
            'html': html,
            'options': {
                'format': 'A4',
                'margin': {'top': '20mm', 'bottom': '20mm',
                           'left': '15mm', 'right': '15mm'},
                'print_background': True,
            },
        },
        timeout=30,
    )
    resp.raise_for_status()
    return resp.content
<?php
// PHP
function generatePdf(string $html): string {
    $apiKey = getenv('FUNBREW_PDF_API_KEY');
    $ch = curl_init('https://pdf.funbrew.cloud/api/v1/pdf/generate');
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST           => true,
        CURLOPT_TIMEOUT        => 30,
        CURLOPT_HTTPHEADER     => [
            'X-API-Key: ' . $apiKey,
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS => json_encode([
            'html'    => $html,
            'options' => ['format' => 'A4', 'print_background' => true],
        ]),
    ]);
    $body   = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    if ($status !== 200) throw new RuntimeException("PDF API error: HTTP $status");
    return $body;
}

Pros:

  • Zero infrastructure management — start with an API key
  • Full Chromium rendering (same CSS support as Puppeteer)
  • Auto-scaling for concurrent requests
  • No cold starts on your side
  • Built-in Japanese and CJK fonts
  • Small Docker images (no Chromium on your servers)
  • Webhooks, template engine, and email delivery built in

Cons:

  • Per-request cost at scale
  • External service dependency
  • HTML is sent to a third-party server (review your data policy)

Best for: Most production applications where PDF generation is a real feature, serverless environments, and teams that do not want to maintain Chromium infrastructure. Try the Playground to verify rendering quality before committing.

Choosing the Right Method

Decision Guide

Is automation required?
  No → Browser print dialog

Is JavaScript execution before export required?
  Yes → Puppeteer, Playwright, or Managed API

What is your primary language?
  Python (no JS needed) → WeasyPrint or Managed API
  Python (JS needed)   → Playwright (Python) or Managed API
  Node.js              → Puppeteer, Playwright, or Managed API
  PHP / Ruby / Go      → Managed API or Gotenberg

Do you need to convert Office files too?
  Yes → Gotenberg

Are you on serverless (Lambda, Vercel, CF Workers)?
  Yes → Managed API strongly recommended

Is volume < 30 PDFs/month?
  Yes → Managed API free tier or Puppeteer

Do you have a strict policy against sending HTML externally?
  Yes → Self-hosted Puppeteer, Playwright, or Gotenberg

Do you want zero infrastructure maintenance?
  Yes → Managed API

Summary Table

Method Setup Memory Serverless CJK Cost
Browser print None N/A Native Free
wkhtmltopdf Low Low Hard Manual Free (deprecated)
Puppeteer High 200–500 MB Hard Manual Server cost
Playwright High 200–500 MB Hard Manual Server cost
WeasyPrint Medium Low Easy Manual Free
Gotenberg Docker High Hard Docker config Free (self-hosted)
Managed API None < 10 MB Easy Built-in Per request

Conclusion

In 2026, the managed PDF API is the default recommendation for new production applications. It delivers the same Chromium rendering quality as Puppeteer with none of the operational overhead. The only strong reasons to self-host are data-residency requirements or a need to run application JavaScript before export that cannot be moved to server-side rendering.

For teams already running Puppeteer or wkhtmltopdf at scale, a managed API typically becomes cost-effective once you account for maintenance engineering hours. Test your templates in the Playground and check the free plan before deciding.

Related

Powered by FUNBREW PDF