Skip to content
CastPDF

CastPDF vs Puppeteer

Puppeteer is a free library that drives Chrome, so you can print any page to PDF on your own servers. CastPDF does the same printing for you, plus page numbers, repeating table headers and stored templates. Running Chrome yourself costs servers and upkeep, which our HTML to PDF API removes for a monthly fee.

  • Updated October 2026
  • Prices checked October 2026
  • 100 free PDFs a month

Build or buy, in two paragraphs

Puppeteer is an open-source JavaScript library, under the Apache-2.0 licence, that controls Chrome or Firefox. Calling page.pdf() prints whatever the browser shows. The library is free, and you control every step. You also own every server, every crash and every Chrome upgrade.

CastPDF runs that browser work for you. You send HTML, or a template ID and JSON, and get a PDF back. It renders in Chromium too, adds a print step for long documents, and costs $19 a month for 2,500 PDFs. Under 100 a month it is free.

Prices side by side

The cheapest monthly plan that includes each volume, from each vendor's public pricing page (checked October 2026, before tax). Where a vendor sells extra documents on a smaller plan, that can cost less than the plan shown; the text below works it out where it matters. CastPDF counts one PDF per document, whatever its page count.

Monthly price at three volumes
PDFs a monthCastPDFPuppeteer
1,000$19 (Starter)Free software; you run the servers
10,000$49 (Growth)Free software; you run the servers
50,000$129 (Pro)Free software; you run the servers
Free plan100 PDFs a monthFree software

What running Puppeteer really costs

The library has no price tag, but the system around it does. When teams add up a self-hosted PDF service, these are the items that tend to appear.

  • Servers sized for a browser. Each Chrome process needs a lot of memory, and a busy hour needs several at once. You pay for that capacity all month, even when nobody is printing.
  • Memory that creeps up. A page or browser that is not closed after an error keeps its memory. Long-running services need careful cleanup, health checks and regular restarts.
  • Chrome updates. Each Puppeteer release is tested against a specific browser version. Upgrades bring security fixes, but also small layout changes you have to catch before customers do.
  • Fonts in containers. Slim Docker images ship with few fonts. Missing fonts show up as fallback text or empty boxes, so you install font packages and keep them current.
  • Concurrency. Ten invoices at the same second means a queue or a pool of browsers. Without one, requests time out or the process runs out of memory.
  • Untrusted HTML. If customers can shape the HTML, the browser can fetch URLs on your network. You need a sandbox and request filtering around it.

None of this is exotic, and plenty of teams run it well. It is simply engineering time that never shows up on an invoice. If one engineer spends two days a quarter on the PDF service, that time often costs more than a year of a small API plan.

Headers, footers and page numbers

Puppeteer handles headers and footers through two options of page.pdf(): headerTemplate and footerTemplate. They only appear with displayHeaderFooter: true. Inside them, elements with the classes pageNumber and totalPages are filled with the numbers.

These templates are separate snippets of HTML, so the styles of your page do not reach them. Most people style them inline and make room for them with the PDF margins. The guide to headers and footers in Puppeteer covers the details.

CastPDF uses the CSS standard for the same job: @page margin boxes in print mode. The header lives in your stylesheet, next to the rest of your design. It can print the current chapter title through string-set, and it can skip the cover with @page :first.

A footer with page numbers, in your own CSS
@page {
  margin: 22mm 16mm 24mm;
  @top-left { content: string(chapter); font-size: 8.5pt; color: #475569; }
  @bottom-right { content: "Page " counter(page) " of " counter(pages); font-size: 8.5pt; }
}
@page :first {
  @top-left { content: none; }
}
h2 { string-set: chapter content(text); }

Long tables and page breaks

Chrome repeats a plain <thead> when a table runs onto a new page, so simple cases work in Puppeteer as well. The harder part is everything around the table: rows split in half, a totals box stranded on its own page, or a block that is quietly cut off.

CastPDF adds rules for that in every document. Table rows, images and anything with the keep-together class avoid page breaks inside them. In print mode the header and its <colgroup> repeat on each continued page. If something would still be cut off, you get a content_lost error instead of a PDF with missing lines. The guide to repeating table headers shows the markup.

What CastPDF costs as you grow

The price table above lists the plan that covers each volume. Between plans, a smaller plan with overage is sometimes cheaper. At 20,000 PDFs a month, Growth plus overage at $6.00 per 1,000 comes to $109. That beats the $129 Pro plan, and it stays inside the default spending cap.

You pay per document, never per page, so a 30-page report costs the same as a one-page receipt. Unused PDFs on paid plans roll over for one month. We email you at 80% and 100% of your allowance, and a spending cap stops overage at a limit you choose. The pricing page has every plan, up to Scale with 200,000 PDFs a month.

When Puppeteer is the better choice

  • You already run it, and it is stable. If your service has worked for a year and nobody touches it, a move saves little. Keep it, and revisit when the next Chrome upgrade hurts.
  • You need the whole browser. Puppeteer can log in, click, fill forms and wait for a selector before printing. CastPDF takes HTML or a template and prints it; it does not browse to your pages.
  • You also need screenshots. Puppeteer captures PNG and JPEG images of pages and elements. CastPDF makes PDFs only.
  • You print at very high volume. Millions of documents a month on your own hardware can cost less than any per-document price. CastPDF also limits how many PDFs render at the same time per team: Free 1, Starter 2, Growth 3, Pro 4 and Scale 4.
  • No document may leave your network. In an air-gapped system, or under rules that forbid calls to outside services, a library inside your own walls is the only option.

Replacing page.pdf() with one request

Most Puppeteer PDF code follows the same pattern: launch a browser, set the HTML, print, close. Here is a typical version, with a header and a numbered footer.

Before: Puppeteer (report.mjs)
import puppeteer from 'puppeteer';
import { readFile } from 'node:fs/promises';

const html = await readFile('report.html', 'utf8');
const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.setContent(html, { waitUntil: 'networkidle0' });
  await page.pdf({
    path: 'report.pdf',
    format: 'A4',
    printBackground: true,
    margin: { top: '25mm', right: '15mm', bottom: '20mm', left: '15mm' },
    displayHeaderFooter: true,
    headerTemplate: '<div style="font-size:9px;width:100%;text-align:right;margin-right:15mm">Quarterly report</div>',
    footerTemplate: '<div style="font-size:9px;width:100%;text-align:center">Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>',
  });
} finally {
  await browser.close();
}

The CastPDF version sends the same HTML. The header and footer move into CSS as margin boxes, and the margins become one string in CSS order: top, sides, bottom.

After: CastPDF (report.mjs, Node.js 20 or later)
import { readFile, writeFile } from 'node:fs/promises';

const html = await readFile('report.html', 'utf8');
const css = `
@page {
  @top-right { content: "Quarterly report"; font-size: 7pt; }
  @bottom-center { content: "Page " counter(page) " of " counter(pages); font-size: 7pt; }
}`;

const res = await fetch('https://api.castpdf.com/v1/pdf', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CASTPDF_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': 'report-2026-q3',
  },
  body: JSON.stringify({ html, css, mode: 'print', format: 'A4', margins: '25mm 15mm 20mm', filename: 'report.pdf' }),
  signal: AbortSignal.timeout(60_000),
});
if (!res.ok) {
  const { error } = await res.json();
  throw new Error(`${error.code}: ${error.message}`);
}
await writeFile('report.pdf', Buffer.from(await res.arrayBuffer()));
console.log('pages:', res.headers.get('x-pages'));
Puppeteer options and their CastPDF equivalents
PuppeteerCastPDF
page.setContent(html)The html field (or a stored template with template_id and data)
format: 'A4', landscape: trueformat and orientation
margin: { top, right, bottom, left }margins as one string, such as "25mm 15mm 20mm"
printBackground: trueAlways on; nothing to set
headerTemplate, footerTemplate@page margin boxes in your CSS, with "mode": "print"
pageNumber, totalPages classescounter(page) and counter(pages)
waitUntil: 'networkidle0'The renderer waits for fonts and loads images within its limits
pageRangesNo equivalent: the whole document is rendered
  1. Get a free test key. Sign up and create a test key; the quickstart takes about five minutes. Test PDFs are free and watermarked.
  2. Send your existing HTML. Use the "After" code with your own HTML file. Keep mode set to print if you need margin boxes or long tables.
  3. Move the header and footer. Rewrite the two Puppeteer templates as @page margin boxes. Remove their inline sizes and let your CSS style them.
  4. Compare the PDFs. Open both files side by side. Check fonts, the first and last page, and any table that crosses a page.
  5. Switch to a live key and retire the browser. When the output matches, use a live key in production. Then remove Chrome from your images and delete the browser pool code.

Sources

Every fact about another product on this page comes from that product's own pages, checked October 2026. CastPDF's prices and limits come from our pricing page.

What Puppeteer publishes
PuppeteerWhat it saysSource
What it isA JavaScript library that controls Chrome or Firefoxpptr.dev
LicenceApache-2.0github.com/puppeteer/puppeteer
Headers and footerspage.pdf() takes headerTemplate and footerTemplate with pageNumber and totalPages classes (needs displayHeaderFooter)pptr.dev/api/puppeteer.pdfoptions
FAQ

Common questions

Is it cheaper to run Puppeteer myself than to pay for a PDF API?

At very high volume, often yes, if you count only servers. At low and medium volume, the engineering time to keep Chrome healthy usually costs more than an API plan. Add up servers, upgrades and on-call time before you decide.

What usually goes wrong when Puppeteer runs in production?

The common problems are memory that grows when pages are not closed, fonts missing from container images, and timeouts when many PDFs are requested at once. Each one is fixable. Together they are the upkeep you take on.

Does CastPDF use Chrome to render PDFs?

It uses Chromium, the open-source project Chrome is built on. In print mode, Paged.js first splits the content into pages, then Chromium prints them. You do not install or update anything.

Can I keep my Puppeteer header and footer templates?

Not as they are. You rewrite them as CSS margin boxes, which usually takes a few minutes. The page number classes become the page and pages counters.

Can CastPDF take screenshots of pages like Puppeteer?

No. CastPDF makes PDFs only, and it renders the HTML you send rather than visiting a website. Keep Puppeteer or another browser tool for screenshots.

How many PDFs can CastPDF render at the same moment?

It depends on the plan: Free 1, Starter 2, Growth 3, Pro 4 and Scale 4 at a time per team. Requests over that number get a rate limit error with a one-second retry hint. Retry them and they go through.

Make your first PDF in 5 minutes

Pick a template, add your details and download your PDF. You get 100 free PDFs every month, and you don’t need a card.