Back to blog
pdf invoiceshtml to pdf apiscreenshot apiinvoice automationdeveloper tools

Turning HTML Templates Into PDF Invoices via API

Learn how to generate PDF invoices from HTML templates using an API — with real code examples, layout tips, and pitfalls to avoid.

2026-08-106 min read

Every SaaS product eventually needs to send invoices. The tricky part isn't the billing logic — Stripe or your payment processor already handles that. The tricky part is turning invoice data into a clean, printable PDF that looks the same whether it's opened in Gmail, Apple Preview, or a corporate accounting system.

Most teams get here after trying (and abandoning) two or three approaches. Let's skip straight to what actually works.

Why HTML Templates Beat Native PDF Libraries

You have two broad options for generating invoice PDFs:

  • Native PDF libraries like PDFKit, jsPDF, or ReportLab, where you draw text and boxes at specific coordinates.
  • HTML-to-PDF rendering, where you build the invoice as a normal webpage and convert it to PDF.

Native libraries feel appealing at first because there's no browser involved. But invoices have tables, dynamic row counts, wrapping text, logos, and multi-page overflow — all things CSS handles natively and PDF-drawing libraries handle painfully. You end up manually calculating x/y positions and re-implementing flexbox by hand.

HTML templates let you use the layout tools you already know: flexbox, grid, `@page` CSS rules, web fonts, and standard CSS classes. If you can build an invoice page in HTML/CSS, you can generate a PDF invoice — you just need something to render that HTML and capture it as PDF.

The Architecture: Template → Render → PDF

The pattern is the same regardless of your stack:

  1. Build an HTML invoice template with placeholder variables (customer name, line items, totals, due date).
  2. At invoice-generation time, populate the template with real data server-side.
  3. Host or serve that populated HTML somewhere reachable by URL, or generate it as a string.
  4. Send it to a rendering API that converts HTML → PDF.
  5. Store or email the resulting PDF file.

This is exactly the kind of job a screenshot/rendering API like PxShot is built for — it takes a URL (or raw HTML), renders it in a real browser engine, and returns a PDF, PNG, JPEG, or WebP via a single HTTP request. No headless browser to install, patch, or babysit.

Step 1: Build the Invoice Template

Keep it as a static HTML file first, then templatize with your backend's rendering engine (Handlebars, EJS, Jinja2, whatever you use for emails).

<!-- invoice-template.html -->
<html>
<head>
  <style>
    body { font-family: 'Inter', sans-serif; padding: 40px; color: #1a1a1a; }
    .header { display: flex; justify-content: space-between; margin-bottom: 40px; }
    table { width: 100%; border-collapse: collapse; margin-top: 20px; }
    th, td { text-align: left; padding: 8px 0; border-bottom: 1px solid #eee; }
    .total-row td { font-weight: 700; border-top: 2px solid #333; }
    @page { size: A4; margin: 20mm; }
  </style>
</head>
<body>
  <div class="header">
    <div><h1>Invoice #{{invoiceNumber}}</h1><p>Due: {{dueDate}}</p></div>
    <div><img src="{{logoUrl}}" width="120" /></div>
  </div>
  <table>
    <tr><th>Description</th><th>Qty</th><th>Price</th></tr>
    {{#each lineItems}}
    <tr><td>{{this.name}}</td><td>{{this.qty}}</td><td>${{this.price}}</td></tr>
    {{/each}}
    <tr class="total-row"><td colspan="2">Total</td><td>${{total}}</td></tr>
  </table>
</body>
</html>

The @page rule matters — it controls PDF page size and margins independently of screen CSS, which most developers skip until their first invoice prints with 0.5-inch margins and cut-off tables.

Step 2: Render It Server-Side

Populate the template with your invoice data using your backend language of choice, then serve it at a temporary, authenticated URL (e.g. /internal/invoices/render/:id) that your rendering API can fetch.

Step 3: Call the Rendering API

With PxShot, converting that rendered HTML page into a PDF is a single request:

curl "https://api.pxshot.dev/capture?url=https://yourapp.com/internal/invoices/render/9231&format=pdf&fullPage=true"   -H "Authorization: Bearer YOUR_API_KEY"   -o invoice-9231.pdf

You get a real PDF file back — the same one a headless Chrome instance would produce, minus the infrastructure. Attach it to the invoice email or store it in S3/R2 for later retrieval.

Handling the Details That Break Invoices in Production

Multi-Page Overflow

Invoices with 30+ line items will overflow onto a second page. Test this early:

  • Set fullPage=true (or your renderer's equivalent) so content isn't clipped to one viewport.
  • Use page-break-inside: avoid; on table rows so a single line item never splits across two pages.
  • Repeat the table header on each page with thead { display: table-header-group; }.

Font Rendering

Web fonts loaded via @font-face or Google Fonts links need time to load before the render happens. If your PDF renderer captures too early, you'll get fallback system fonts on some invoices and not others — a subtle bug that's hard to catch in casual testing. Prefer renderers that wait for network idle before capturing, and self-host fonts when possible to avoid CDN latency entirely.

Currency and Locale Formatting

Format currency and dates server-side before injecting into the template, not client-side with JS that may not execute in time. `Intl.NumberFormat` on the backend is more reliable than relying on the rendering engine to run your JavaScript correctly before the snapshot is taken.

Logo and Image Loading

Remote logo URLs (especially from customer-uploaded assets) can be slow or occasionally down. Two safeguards:

  • Inline small logos as base64 data URIs directly in the HTML to remove a network dependency entirely.
  • Set a reasonable timeout on the rendering call so a broken image URL doesn't hang your invoice generation pipeline.

Automating the Whole Pipeline

Once the template and render call work manually, wire it into your billing events:

  1. Payment succeeds (Stripe webhook, etc.) → generate invoice HTML with order data.
  2. POST the rendered page URL to PxShot's /capture endpoint with format=pdf.
  3. Store the returned PDF binary in object storage, keyed by invoice ID.
  4. Attach it to the confirmation email or make it downloadable from the customer dashboard.

This same rendering call can double as your monthly statement generator, quote/estimate PDF creator, or receipt exporter — same template pattern, different data source.

When to Skip Building This Yourself

If you're testing this at small scale, running a full headless Chrome instance just to convert invoice HTML to PDF is overkill — it's slow to cold-start, memory-hungry, and one more service to patch and monitor. An HTTP-based rendering API removes that entire layer: you send a URL or HTML, you get back a PDF, and scaling is someone else's problem.

Start with a free PxShot API key at pxshot.dev — the free tier is enough to build and test your full invoice pipeline before you commit to anything.