ScreenshotNeo

BlogHTML to image & PDF

How to Generate PDFs for Invoices and Reports

Learn when to use code, HTML templates, PDF APIs, or billing platforms to generate reliable invoice and report PDFs.

By the ScreenshotNeo team1 October 20268 min read

Generate a PDF from the representation you already have: use a code-first library for structured data, render HTML when your invoice or report already exists as a template, use a hosted API when you want managed rendering, or use your billing platform when it already owns the invoice records.

The right choice depends on your input format, programming language, layout requirements, deployment model, document volume, licensing, and whether an existing billing system already provides PDF generation.

Choose an approach

Approach Best fit Main trade-off
Application code with pdf-lib JavaScript or Deno applications that need PDF bytes directly You own layout, pagination, fonts, and table logic
HTML to PDF Invoices and reports already represented by HTML templates Rendering depends on browser or service CSS support
QuestPDF C# applications needing fluent, code-first layouts Review current licensing eligibility before shipping
Billing-platform PDFs Teams whose billing system already stores invoice records and templates Availability depends on product edition and license
Managed document API Teams that prefer an HTTP API over operating a renderer Data leaves your process and quotas, terms, and costs apply

Compare candidates on input format, runtime support, page-break behavior, headers and footers, fonts, batch generation, hosting, rate limits, licensing, and data handling.

Generate a PDF in JavaScript with pdf-lib

Deno’s official PDF example uses pdf-lib to create a document, add a page, embed fonts, draw text, serialize the document, and write the bytes to disk. The same pattern works for a small invoice or report generated from application data.

Complete Deno example

import { PDFDocument, StandardFonts, rgb } from 'npm:pdf-lib';

const invoice = {
  number: 'INV-1007',
  customer: 'Acme Example Ltd',
  date: '2026-10-01',
  currency: 'USD',
  items: [
    { description: 'Design work', quantity: 10, unitPrice: 125 },
    { description: 'Hosting', quantity: 1, unitPrice: 40 }
  ]
};

const pdf = await PDFDocument.create();
const page = pdf.addPage([595.28, 841.89]); // A4 points
const font = await pdf.embedFont(StandardFonts.Helvetica);
const bold = await pdf.embedFont(StandardFonts.HelveticaBold);
const { width, height } = page.getSize();
const margin = 48;
let y = height - margin;
const money = value => `${invoice.currency} ${value.toFixed(2)}`;
const subtotal = invoice.items.reduce((sum, item) => sum + item.quantity * item.unitPrice, 0);
const tax = subtotal * 0.2;
const total = subtotal + tax;

page.drawText('INVOICE', { x: margin, y, size: 24, font: bold, color: rgb(0.1, 0.1, 0.1) });
y -= 34;
page.drawText(invoice.number, { x: margin, y, size: 11, font });
page.drawText(`Date: ${invoice.date}`, { x: width - 180, y, size: 11, font });
y -= 28;
page.drawText(`Bill to: ${invoice.customer}`, { x: margin, y, size: 12, font: bold });
y -= 35;

page.drawText('Description', { x: margin, y, size: 10, font: bold });
page.drawText('Qty', { x: 350, y, size: 10, font: bold });
page.drawText('Amount', { x: 450, y, size: 10, font: bold });
y -= 18;
for (const item of invoice.items) {
  const amount = item.quantity * item.unitPrice;
  page.drawText(item.description, { x: margin, y, size: 10, font });
  page.drawText(String(item.quantity), { x: 350, y, size: 10, font });
  page.drawText(money(amount), { x: 450, y, size: 10, font });
  y -= 18;
}

y -= 12;
page.drawText(`Subtotal: ${money(subtotal)}`, { x: 350, y, size: 10, font });
y -= 16;
page.drawText(`Tax: ${money(tax)}`, { x: 350, y, size: 10, font });
y -= 20;
page.drawText(`Total: ${money(total)}`, { x: 350, y, size: 12, font: bold });

const bytes = await pdf.save();
await Deno.writeFile('invoice.pdf', bytes);

This example demonstrates drawing and arithmetic, but production documents need a layout strategy. Test long customer names, long descriptions, many line items, multi-page tables, repeated headers, embedded fonts, logos, page numbering, and searchable text. Keep totals derived from the same trusted accounting data used by the application.

Render an HTML invoice to PDF with Node.js

HTML-to-PDF is usually the most direct route when your team already has an invoice template. A browser renderer gives you familiar HTML and CSS layout primitives. The example below uses Playwright locally; host Chromium with your application or run it in a worker.

import { chromium } from 'playwright';
import { writeFile } from 'node:fs/promises';

const invoice = {
  number: 'INV-1007',
  customer: 'Acme Example Ltd',
  items: [
    { description: 'Design work', quantity: 10, unitPrice: 125 },
    { description: 'Hosting', quantity: 1, unitPrice: 40 }
  ]
};
const subtotal = invoice.items.reduce((s, i) => s + i.quantity * i.unitPrice, 0);
const tax = subtotal * 0.2;
const total = subtotal + tax;
const money = n => `$${n.toFixed(2)}`;
const rows = invoice.items.map(i => `${i.description}${i.quantity}${money(i.quantity * i.unitPrice)}`).join('');
const html = `

Invoice

${invoice.number}
Bill to: ${invoice.customer}

${rows}
DescriptionQtyAmount

Subtotal: ${money(subtotal)}

Tax: ${money(tax)}

Total: ${money(total)}

`; const browser = await chromium.launch(); const page = await browser.newPage(); await page.setContent(html, { waitUntil: 'networkidle' }); await page.pdf({ path: 'invoice.pdf', format: 'A4', printBackground: true, preferCSSPageSize: true }); await browser.close();

For a hosted HTML-to-PDF workflow, Adobe documents conversion of static and dynamic HTML, ZIP, and URL inputs through its PDF Services API. Other documented services include PDFMonkey’s template API, pdfmyhtml’s JSON and HTML/URL generation, and HTMLPDF.dev’s HTML-or-URL endpoint. Evaluate current API terms, privacy, quotas, rate limits, supported CSS, and output behavior before selecting one.

Generate reports and invoices in C# with QuestPDF

QuestPDF provides a fluent, component-based C# API for reports, invoices, and exports. Its repository documentation states that the library is free for individuals, nonprofits, open-source projects, and organizations under $1 million in annual gross revenue. Check the current license and confirm that your organization and deployment qualify.

using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;

QuestPDF.Settings.License = LicenseType.Community;
var invoice = new InvoiceDocument();
invoice.GeneratePdf("invoice.pdf");

public class InvoiceDocument : IDocument
{
    public DocumentMetadata GetMetadata() => DocumentMetadata.Default;

    public void Compose(IDocumentContainer container)
    {
        container.Page(page =>
        {
            page.Margin(40);
            page.Header().Text("INVOICE INV-1007").FontSize(24).Bold();
            page.Content().Column(column =>
            {
                column.Spacing(8);
                column.Item().Text("Bill to: Acme Example Ltd");
                column.Item().Table(table =>
                {
                    table.ColumnsDefinition(columns =>
                    {
                        columns.RelativeColumn(3);
                        columns.ConstantColumn(60);
                        columns.ConstantColumn(100);
                    });
                    table.Header(header =>
                    {
                        header.Cell().Text("Description").Bold();
                        header.Cell().Text("Qty").Bold();
                        header.Cell().Text("Amount").Bold();
                    });
                    table.Cell().Text("Design work"); table.Cell().Text("10"); table.Cell().Text("$1,250.00");
                    table.Cell().Text("Hosting"); table.Cell().Text("1"); table.Cell().Text("$40.00");
                });
                column.Item().AlignRight().Text("Total: $1,548.00").Bold();
            });
            page.Footer().AlignCenter().Text(text => text.CurrentPageNumber());
        });
    }
}

Use an existing billing platform

If your billing system already owns invoice records, templates, taxes, and delivery, use its native PDF feature before adding another renderer. Salesforce documents individual and batch invoice PDF creation with templates, but its help page lists Lightning Experience and Enterprise, Unlimited, and Developer Editions with a Revenue Cloud Billing license as availability requirements. Confirm the edition and license for your account.

Design for pagination and correctness

  1. Separate data from presentation. Calculate line totals, discounts, taxes, and grand totals in application code or your accounting source.
  2. Define a page policy. Choose paper size, margins, orientation, header and footer behavior, and page numbering.
  3. Handle growth. Add explicit rules for long descriptions, table rows that split across pages, repeated table headers, and very large reports.
  4. Embed required fonts. This improves portability and avoids missing glyphs for non-Latin names and currencies.
  5. Preserve traceability. Store the source invoice or report ID beside the generated file and make regeneration deterministic.
  6. Validate representative documents. Include long addresses, many line items, multiple currencies when applicable, logos, tax and discount combinations, and multi-page output.

Or skip the browser setup

ScreenshotNeo can render a public invoice or report URL directly to PDF with one request. Its PDF options include paper size, margins, landscape mode, and page ranges; it also supports custom CSS and JavaScript, waiting for a selector, delay, or network idle, custom headers and cookies, and blocking unwanted requests.

See the ScreenshotNeo API documentation for the full request options.

cURL

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

Python

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

Node.js

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

Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response identifies the page verdict and billing result in X-Page-Verdict and X-Billed headers. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Troubleshooting

Symptom Likely cause Fix
Text or symbols are missing Font is unavailable or not embedded Embed a font, verify glyph coverage, and test non-Latin data.
Rows overlap or disappear Manual coordinates or page-break rules do not account for content height Use a flow layout, measure text, and test long descriptions and multi-page tables.
PDF is blank HTML has not rendered, assets are blocked, or the URL requires authentication Wait for a selector or network idle, verify asset URLs, and pass required headers or cookies.
Browser job is slow Large images, third-party resources, or expensive client-side rendering Resize images, block unnecessary requests, wait on a specific readiness selector, and cache stable output.
Wrong paper size or clipped content CSS @page, margins, and renderer settings conflict Set one authoritative paper-size policy and inspect print margins and bleed.
Invoice totals differ Rounding or tax logic is duplicated in the template Calculate totals once in trusted application data and render those values.
External API returns an error Invalid key, malformed URL, quota, timeout, or unsupported option Check the response body and status, validate parameters, use bounded retries for transient failures, and monitor usage.

Performance, reliability, and cost

  • Local generation: avoids network transfer but consumes application CPU and memory. Isolate large reports in workers.
  • Browser rendering: reuse browser processes where safe, limit concurrency, and avoid loading resources that do not appear in the document.
  • Hosted APIs: account for request latency, quotas, rate limits, retries, storage, and data-transfer requirements. Do not claim a vendor is faster without your own measurements.
  • Caching: cache only when the source data, template, assets, and access rules are unchanged. Include a document version or invoice revision in the cache key.
  • Retries: retry network and temporary service failures with exponential backoff and an idempotent document ID. Do not blindly retry validation errors.
  • Billing: track pages, documents, and batch size separately. For ScreenshotNeo, only clean shots are billed; failed loads, bot checks, blank pages, timeouts, and cache hits are free.

Deployment checklist

  • Confirm invoice and report data comes from the authoritative record.
  • Test long names, addresses, descriptions, many rows, tax and discount combinations, currencies, fonts, and multi-page output.
  • Verify page size, margins, orientation, headers, footers, and page numbers.
  • Confirm PDFs are searchable if users need copy and search.
  • Review jurisdiction-specific invoice, tax, retention, and e-invoicing requirements with a qualified adviser; the implementation sources do not establish a universal legal checklist.
  • Protect invoice URLs and API keys, and avoid exposing private data in public render endpoints.
  • Record the source record ID, template version, generation time, and renderer version.

FAQ

Should I generate an invoice PDF from HTML or draw it with code?

Use HTML when you already maintain a web template and CSS. Use a code-first library when structured data and in-process PDF bytes are the primary requirements.

Can a billing platform generate PDFs in batches?

Some can. Salesforce documents individual and batch invoice PDF generation, subject to the listed edition and Revenue Cloud Billing licensing requirements.

Is a PDF API suitable for confidential invoices?

It can be, but review the provider’s data handling, retention, security terms, regional processing, authentication, and deletion controls before sending confidential data.

How do I make a report accessible?

Choose a renderer that supports the accessibility features you need, then validate reading order, text extraction, contrast, and tagging with representative documents.