ScreenshotNeo

BlogHTML to image & PDF

How to Create PDF Invoices from HTML

Build an invoice from application data, style it for print, and generate a PDF with Puppeteer. Includes runnable code, layout checks, and e-invoice guidance.

By the ScreenshotNeo team4 October 202610 min read

To create a PDF invoice from HTML, render authoritative invoice data into a safe HTML template, apply print-specific CSS, then use a browser PDF renderer such as Puppeteer. Keep calculations in your application, wait for required assets to load, and validate the resulting pages using realistic invoice data. A PDF is a visual document; it does not automatically meet structured e-invoicing requirements.

1. Keep invoice data separate from presentation

Build the invoice from application data rather than calculating or hard-coding financial values in the template. Your data model may include:

  • Seller and buyer details
  • Invoice identifier, issue date, and due date
  • Line item descriptions, quantities, unit prices, discounts, and applicable taxes
  • Subtotal, tax totals, amount due, currency, and payment instructions
  • Any additional fields required for the relevant transaction and jurisdiction

There is no universal field checklist established here: requirements depend on location and transaction type. Verify them with the applicable official guidance. Compute totals in application code using appropriate decimal or integer minor-unit arithmetic, then pass the finished values to the view. The renderer should format the invoice, not determine what the customer owes.

2. Create an HTML invoice template

Use semantic HTML and a table for line items so the reading order is clear. Escape or safely encode any untrusted values before inserting them into markup. For example, in a server-side template, use its escaping features rather than concatenating customer-supplied strings into HTML.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Invoice INV-1042</title>
  <style>
    :root { font-family: Arial, sans-serif; color: #17202a; }
    * { box-sizing: border-box; }
    body { margin: 0; font-size: 12px; line-height: 1.45; }
    .invoice { max-width: 800px; margin: 32px auto; padding: 36px; }
    .top, .parties { display: flex; justify-content: space-between; gap: 32px; }
    .parties { margin: 36px 0; }
    h1 { margin: 0; font-size: 28px; }
    h2 { font-size: 13px; margin: 0 0 8px; }
    p { margin: 2px 0; }
    table { width: 100%; border-collapse: collapse; }
    th, td { padding: 9px 8px; border-bottom: 1px solid #d8dee4; text-align: left; }
    th:last-child, td:last-child { text-align: right; }
    .totals { width: 280px; margin: 18px 0 0 auto; }
    .totals td { border: 0; }
    .due { font-weight: bold; font-size: 15px; }
    .payment { margin-top: 36px; }
    @page { size: A4; margin: 16mm; }
    @media print {
      body { font-size: 11px; }
      .invoice { max-width: none; margin: 0; padding: 0; }
      .screen-only { display: none !important; }
      thead { display: table-header-group; }
      tr { break-inside: avoid; }
      .totals { break-inside: avoid; }
      * { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
    }
  </style>
</head>
<body>
  <main class="invoice">
    <header class="top">
      <div><h1>Invoice</h1><p>INV-1042</p></div>
      <div><p>Issued: 2026-10-04</p><p>Due: 2026-10-18</p></div>
    </header>
    <section class="parties">
      <div><h2>From</h2><p>Northstar Studio</p><p>100 Market Street</p></div>
      <div><h2>Bill to</h2><p>Example Customer</p><p>20 River Road</p></div>
    </section>
    <table>
      <thead><tr><th>Description</th><th>Qty</th><th>Unit price</th><th>Amount</th></tr></thead>
      <tbody>
        <tr><td>Design services</td><td>4</td><td>$125.00</td><td>$500.00</td></tr>
        <tr><td>Hosting, October</td><td>1</td><td>$40.00</td><td>$40.00</td></tr>
      </tbody>
    </table>
    <table class="totals">
      <tbody>
        <tr><td>Subtotal</td><td>$540.00</td></tr>
        <tr><td>Tax</td><td>$43.20</td></tr>
        <tr class="due"><td>Amount due</td><td>$583.20</td></tr>
      </tbody>
    </table>
    <section class="payment"><h2>Payment instructions</h2><p>Pay by bank transfer using invoice INV-1042 as the reference.</p></section>
  </main>
</body>
</html>

The example contains illustrative values only. In production, render escaped values from your application and format dates and currency according to your product’s rules.

3. Generate the PDF with Puppeteer

Puppeteer’s Page.pdf() prints using the print CSS media type by default. It supports page format, margins, headers and footers, background printing, page ranges, and CSS page-size behavior. The following Node.js example loads an existing invoice route and writes a PDF file.

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({ headless: true });
try {
  const page = await browser.newPage();
  await page.goto('http://localhost:3000/invoices/INV-1042/print', {
    waitUntil: 'networkidle0',
    timeout: 30000,
  });

  await page.pdf({
    path: 'invoice-INV-1042.pdf',
    format: 'A4',
    printBackground: true,
    preferCSSPageSize: true,
    margin: { top: '16mm', right: '16mm', bottom: '16mm', left: '16mm' },
  });
} finally {
  await browser.close();
}

Install Puppeteer in your Node project and ensure the deployment environment can run its Chromium browser. The example assumes your application has authenticated and authorized access to the print route; adapt that mechanism to your environment. Do not expose an invoice URL that allows one customer to retrieve another customer’s document.

Relevant PDF options

Option Use
format Select a named paper size such as A4 or Letter. Puppeteer documents Letter as its default format.
margin Set top, right, bottom, and left margins. Coordinate these with CSS @page.
printBackground Include background graphics and colors when the design needs them.
preferCSSPageSize Let CSS @page dimensions take precedence over the API paper format.
displayHeaderFooter, headerTemplate, footerTemplate Add browser-generated page headers or footers. Check their spacing against the content margins.
pageRanges Print selected page ranges when partial output is intended.
scale Adjust printed content scale if needed; validate legibility after changing it.

See the Puppeteer Page.pdf() reference for the current option details. Puppeteer documents that it waits for document.fonts.ready by default. If your page fetches other assets asynchronously, make sure they are ready before rendering as well.

4. Tune print layout and pagination

Print CSS controls the output separately from the screen presentation. MDN documents @media print for print-only styles and @page for page dimensions, orientation, and margins. Choose paper size intentionally for the audiences you serve, and render representative documents in the actual browser and environment you deploy.

  • Hide navigation, buttons, and interactive controls in print mode.
  • Set page size and margins explicitly; avoid assuming a screen-width layout maps cleanly to paper.
  • Use table headers and sensible row break behavior for long item lists.
  • Check whether large descriptions, totals, and payment details split awkwardly between pages.
  • Use background printing and exact print colors only when the invoice design needs them; check the printed result for contrast and readability.
  • Test logos, custom fonts, currency symbols, and long addresses with the same asset-loading path used in production.

CSS break rules are layout hints. Inspect the generated PDF for the cases that matter to your invoice, including a line item that is taller than the available page area and totals near the bottom margin.

5. Validate realistic invoices before delivery

Use a review set that covers the variation your system permits. At minimum, check:

  • Long seller and buyer names, postal addresses, and descriptions
  • Many line items and documents that span multiple pages
  • Discounts, multiple tax rows, zero-tax cases, and rounding boundaries
  • Negative adjustments or credit scenarios if your application supports them
  • Currency symbols and locale-specific number formats
  • Missing optional fields and unusually long payment instructions
  • Page numbers, headers and footers, and totals at page boundaries
  • Whether calculations in the PDF match independently computed application totals

Keep a few representative generated PDFs as review fixtures when you change the template or browser version. This is a practical regression check, not a substitute for checking the business rules that calculate invoice values.

6. Choose a rendering route

An application-managed browser renderer such as Puppeteer gives you control over the browser and template execution, while requiring you to operate that runtime and manage its dependencies. A hosted converter can reduce browser setup, but you need to assess its current pricing, limits, privacy terms, data residency, authentication model, and rendering behavior for your template.

Adobe documents a PDF Services HTML-to-PDF API that accepts static or dynamic HTML, ZIP, and URL inputs. That documentation establishes the input routes; it does not establish comparative performance or suitability for your invoice. Review the provider’s current service terms and test with representative documents before sending customer data to a third party.

7. Know when a PDF is not enough

A PDF made from HTML is a human-readable visual invoice. In its guidance under Directive 2014/55/EU, the European Commission defines an eInvoice as structured data that allows automatic electronic processing, and says unstructured PDF invoices and HTML invoices are not eInvoices under that definition. See the Commission’s eInvoicing guidance.

The Commission’s EN 16931 standards guidance describes a semantic data model for core invoice business terms, their meanings, and rules for their use. If a buyer, procurement process, or local law requires structured e-invoicing, generate the required structured payload and transmission alongside or instead of the visual PDF as applicable. Check the rules for the relevant jurisdiction and transaction; a visually correct PDF does not prove compliance.

8. Performance, reliability, privacy, and cost

  • Performance: Reuse browser processes where your architecture permits instead of starting a new browser for every document, and measure the effect with your own templates and deployment environment. Limit unnecessary remote assets and keep print pages deterministic.
  • Reliability: Set navigation timeouts, handle failed asset loads, close pages and browsers in cleanup paths, and make retries safe. Avoid generating the same invoice concurrently without an idempotent storage strategy.
  • Cost: Account for browser runtime, memory, CPU, storage, and operational maintenance for self-hosting. For hosted APIs, check current pricing and quotas directly; the cited documentation does not establish a cost comparison.
  • Privacy: Invoice pages can contain personal and financial data. Restrict access, avoid logging full rendered HTML or sensitive URLs, and review data handling before sending content to a conversion provider.
  • Correctness: Store the authoritative invoice data and calculations in your application. Preserve the generated artifact according to your own retention and access requirements.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from ScreenshotNeo. For a URL that renders your invoice HTML, one GET request can return a PDF; see the ScreenshotNeo API documentation. For example:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/invoices/INV-1042 -d format=pdf -o invoice.pdf

With Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com/invoices/INV-1042", "format": "pdf"},
    timeout=90,
)
r.raise_for_status()
open("invoice.pdf", "wb").write(r.content)

With Node.js:

const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com/invoices/INV-1042',
  format: 'pdf',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await require('node:fs/promises').writeFile('invoice.pdf', Buffer.from(await res.arrayBuffer()));

Use an invoice URL accessible to the service and protect customer data when deciding how to expose that page. ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers say which verdict and billing status applied. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 shots a month with no card; paid plans start at $5 for 3,000 shots. Sign up for 1,000 free screenshots a month, no card required.

Troubleshooting

Symptom Likely cause Fix
PDF is blank or missing content The page did not finish rendering, or the route returned an error or empty state. Check the route in the same environment, wait for the required content and assets, and fail the job if the page indicates an application error.
Fonts or logo are missing Assets are blocked, unavailable to the browser process, or not loaded before PDF generation. Check asset URLs and access permissions, wait for required assets, and confirm fonts are available in the deployed runtime.
Colors or backgrounds disappear Print output omits backgrounds unless enabled, or print color adjustment changes colors. Enable printBackground and apply print color adjustment where appropriate; inspect readability in the generated PDF.
Content is clipped or unexpectedly scaled Conflicting CSS page size, API format, margins, or wide content. Choose one source of page dimensions, reconcile @page with API options, and check tables and long values at the target paper size.
Rows or totals split awkwardly Content does not fit the remaining printable area, and break rules cannot keep an oversized block together. Review row and totals break styles, shorten or restructure content where possible, and validate multi-page examples.
Navigation times out The page keeps network activity open or relies on a slow external resource. Use a deliberate readiness condition for your route, inspect slow requests, and set a timeout suitable for the environment rather than waiting indefinitely.
PDF data differs from the invoice record Presentation code recalculates values or formats inconsistent source values. Pass authoritative totals from the application and compare rendered values against independent business calculations.
Customer can access someone else’s invoice The print route lacks proper authorization or uses a guessable public identifier. Enforce authorization at the route and storage layers; avoid treating an invoice ID as a secret.

FAQ

Can I create the PDF without saving an HTML file?

Yes. Serve or construct the invoice HTML in memory, then render that page with the browser PDF API. Keep the same escaping, asset-loading, and access-control requirements.

Should invoice arithmetic happen in HTML?

No. Calculate amounts in application logic and render the resulting values. This keeps displayed documents tied to the system’s authoritative invoice data.

Does an HTML invoice PDF satisfy e-invoicing rules?

Not by itself under the European Commission definition cited above. Requirements vary; verify the applicable structured format and transmission rules for your jurisdiction and customer.

Can I use a hosted converter instead of Puppeteer?

Yes. Evaluate current price, quotas, privacy terms, accepted inputs, and output against your own invoice templates before selecting one.