URL to PDF: How to Convert Any Web Page to PDF Programmatically

Five practical methods to convert URLs to PDFs programmatically. From self-hosted solutions to APIs, find the approach that fits your stack.

Converting a web page to a PDF sounds like it should be a one-liner. In practice, it's one of those tasks that reveals hidden complexity the moment you start building: CSS rendering differences, pagination quirks, missing fonts, and pages that load content dynamically with JavaScript.

This guide covers the methods that actually work in 2026, with code you can deploy today.

When Do You Need URL-to-PDF?

The most common scenarios developers encounter:

Invoice and receipt generation. Your web app has a beautiful invoice view in HTML/CSS. Customers expect to download it as a PDF. You need a way to render that HTML into a print-quality document.

Report exports. Dashboards and analytics pages need a "Download as PDF" button. The PDF should look as close to the screen version as possible.

Legal and compliance archival. Some industries require you to keep PDF records of web-based documents — terms of service snapshots, signed agreements rendered in the browser, regulatory filings.

Content distribution. Blog posts, documentation, or guides that users want to read offline or share in a portable format.

Method 1: Puppeteer's page.pdf()

Puppeteer can render any URL in headless Chrome and export the page as a PDF, complete with proper pagination and print styles.

const puppeteer = require('puppeteer');

async function urlToPdf(url) {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto(url, { waitUntil: 'networkidle2' });

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

  await browser.close();
  return pdf; // Buffer
}

This gives you the highest fidelity output — Chrome renders the page exactly as it would on screen, then converts it to PDF. CSS @media print rules are respected, so you can customize the print layout.

The downsides are the same as any Puppeteer deployment: heavy memory footprint, Chrome dependency management, and Docker complexity in production.

Method 2: Playwright's PDF Export

Playwright offers the same capability with a slightly cleaner API:

const { chromium } = require('playwright');

async function urlToPdf(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;
}

The output quality is virtually identical to Puppeteer since both use the same underlying Chromium engine. Choose Playwright if you're already using it elsewhere in your stack.

Method 3: PDF Generation API

If you don't want to manage browser infrastructure, a PDF API handles the rendering server-side:

const fetch = require('node-fetch');

async function urlToPdf(url) {
  const response = await fetch(
    'https://snapapi-production-4f80.up.railway.app/api/pdf',
    {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.SNAPAPI_KEY,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        url: url,
        format: 'A4',
        landscape: false
      })
    }
  );

  return await response.buffer();
}

You can also send raw HTML instead of a URL, which is particularly useful for invoice generation where you're building the document from a template:

const response = await fetch(
  'https://snapapi-production-4f80.up.railway.app/api/pdf',
  {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.SNAPAPI_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      html: '<html><body><h1>Invoice #1234</h1>...</body></html>',
      format: 'A4'
    })
  }
);

Method 4: wkhtmltopdf (Legacy)

Still found in older codebases, wkhtmltopdf is a command-line tool using an outdated WebKit engine. It can't handle modern CSS (flexbox, grid) or JavaScript-heavy pages. If you're starting a new project, skip this entirely. If you're maintaining legacy code that uses it, consider migrating — the rendering differences between wkhtmltopdf and what users see in their browser are significant.

Method 5: Python with requests

If your stack is Python, you can call a PDF API just as easily:

import requests

def url_to_pdf(url, api_key):
    response = requests.post(
        'https://snapapi-production-4f80.up.railway.app/api/pdf',
        headers={
            'X-API-Key': api_key,
            'Content-Type': 'application/json'
        },
        json={
            'url': url,
            'format': 'A4'
        }
    )

    with open('output.pdf', 'wb') as f:
        f.write(response.content)

url_to_pdf('https://example.com', 'sk_your_key_here')

Comparison: Which Method Should You Use?

Factor Puppeteer/Playwright PDF API wkhtmltopdf
Rendering quality Excellent Excellent Outdated
Modern CSS support Full Full Partial
JavaScript rendering Full Full Limited
Infrastructure needed Chrome + server None Binary install
Best for Custom workflows URL/HTML to PDF Legacy systems

Convert URLs to PDF Instantly

Try generating a PDF from any URL right now — no signup, no configuration.

Try Interactive Demo

Tips for Better PDF Output

Use print stylesheets. Add @media print CSS rules to hide navigation, footers, and other elements that don't belong in a PDF. This makes a bigger difference than any tool-level setting.

Set explicit margins. Browser defaults vary. Always specify margins to get consistent spacing across different rendering environments.

Enable background printing. By default, Chrome's PDF export strips background colors and images. Always set printBackground: true to preserve your design.

Test with long content. Pagination is where most PDF generators struggle. Test with pages that span 5+ pages to make sure headers, footers, and page breaks render correctly.

Kevin, Founder of SnapAPI
Kevin
Founder, SnapAPI

Disclosure: This article was written with the help of Claude, an AI assistant by Anthropic.