A Puppeteer and Playwright page.pdf() alternative

If you launch headless Chrome only to print invoices, receipts or reports built from your own data, Quire replaces the browser, the HTML template and the print CSS with one API call that takes JSON and returns a PDF. If you need pixel control over your own HTML, JavaScript-rendered content such as charts, or PDFs of existing web pages, keep Puppeteer or Playwright.

What generating a PDF with a browser involves

Puppeteer and Playwright drive a real browser, and page.pdf() prints the current page:

  • page.pdf() renders the page with print CSS media (Puppeteer docs, Playwright docs), so you maintain print styles alongside the HTML.
  • Headers and footers are separate HTML snippets. Page numbers come from special classes such as pageNumber and totalPages; headers are off by default, and so are background graphics (PDFOptions). In Playwright, scripts in those templates aren’t run and page styles don’t apply to them (docs).
  • In Playwright, PDF generation works only in headless Chromium; other browsers throw “PDF generation is only supported for Headless Chromium” (source).
  • Installing Puppeteer downloads Chrome for Testing, about 170 MB on macOS and 282 MB on Linux, plus a chrome-headless-shell binary (installation guide).
  • On Linux, Chrome needs a long list of system packages, and the Puppeteer team says running it without a sandbox is “strongly discouraged” (troubleshooting), which affects how you build your container.

None of this is a problem if you need a browser. It’s a lot of machinery if what you need is an invoice.

How Quire is different

You send the data. Quire picks a layout, paginates it and returns the PDF:

import { Quire, type ReceiptData } from "quirepdf";
import { writeFile } from "node:fs/promises";

const quire = new Quire(); // reads QUIRE_API_KEY

const receipt: ReceiptData = {
  number: "RCPT-2026-0388",
  paid_on: "3 Oct 2026",
  currency: "$",
  seller: { name: "Tidewater Labs", email: "[email protected]" },
  customer: { name: "Maya Chen", email: "[email protected]" },
  payment: { method: "Visa ending 4242", transaction_id: "ch_3Q8xLm2eZvKYlo2C" },
  items: [
    { description: "Team plan, annual", qty: 1, unit_price: 480 },
    { description: "Extra storage, 100 GB", qty: 2, unit_price: 24 },
  ],
  tax: { label: "Sales tax (10.1%)", rate: 0.101 },
};

const pdf = await quire.render(receipt, { template: "receipt" });
await writeFile("receipt.pdf", pdf.bytes);

Compare that with a typical browser flow: build an HTML template, add print CSS, launch the browser, open a page, set its content, wait for fonts, call page.pdf() with header and footer templates, and close the page, while keeping a pool of browsers healthy in production.

Puppeteer / Playwright page.pdf() Quire
Input Any HTML page or URL A JSON object
You maintain HTML, print CSS, header/footer templates, a browser binary and its OS dependencies The JSON you send
Layout freedom Anything a browser can render Seven built-in layouts
Typed input No; the page renders whatever data it gets TypeScript types and Python TypedDicts per template; invalid data is rejected with field paths
Totals and page numbers You compute and template them Computed by the gallery templates; “x of y” footer built in
Runs In your process or container Hosted API
Paper sizes Letter by default, configurable (PDFOptions) A4
Cost Free, plus your servers Free to build (unlimited test renders, 20 PDFs a month), then from $19 a month

Speed and memory

Quire doesn’t start a browser: it typesets with a compiled engine. Measured on a laptop, October 2026, a one-page invoice rendered in 4.2 ms at the median and a 14-page, 300-item invoice in 48 ms. Over HTTP, 400 requests at a concurrency of 8 had a median of 16.6 ms, and the server used 54 MB of memory afterwards. On the hosted API, add your network round trip. We haven’t benchmarked Chrome on the same machine, so we don’t quote a number for it here; measure your own page.pdf() timings to compare.

Moving from page.pdf() to Quire

  1. Start from the object you pass to your HTML template. That’s your JSON.
  2. Use a gallery template if the document is a standard one. Invoices, receipts, quotes, credit notes, statements and certificates have typed templates that compute totals. npx quirepdf-cli templates receipt lists the fields.
  3. Use the generic layout for everything else. Add title, from, to and currency; objects become key/value sections and arrays of objects become tables. See Templates and layout.
  4. Use PNG for previews. { format: "png", page: 1 } returns an image of one page, which replaces page.screenshot() for thumbnails.
  5. Remove the browser from your image and your dependency list.

When Puppeteer or Playwright is the better choice

  • You need your own design. Quire has seven layouts and no custom template upload yet (it’s on the roadmap). If your brand needs a specific layout, HTML and CSS in a browser give you full control.
  • The content is rendered by JavaScript, such as charts, maps or a single-page app. Quire renders data; it doesn’t run scripts or draw charts.
  • You’re capturing existing web pages or HTML you don’t control. Quire doesn’t take HTML or URLs.
  • You need Letter or custom paper sizes. Quire’s layouts are A4.
  • You can’t send data to a third party, or must run offline. Quire is a hosted API; self-hosting isn’t offered yet. Quire doesn’t store your data or documents, but they do leave your network.
  • You already run browsers for testing or scraping and adding PDF output costs you nothing extra.

If you’re choosing between running a browser yourself and a self-hosted conversion service, see Quire compared with Gotenberg.

Try it

npm install quirepdf
npx quirepdf-cli login [email protected]

The TypeScript SDK reference covers typed templates, PNG previews and errors, and the Node.js invoice guide has a complete route handler.