Get started
PDF generation in Bun: every working option in 2026

PDF generation in Bun: every working option in 2026

How to generate PDFs in Bun in 2026: Puppeteer support, why Playwright is unofficial, pure-JS libraries with no browser, and the hosted API escape hatch.

9 min read

Generating a PDF in Bun works today, with one caveat per tool. Bun runs Puppeteer, so HTML-to-PDF through headless Chromium works. Bun runs pure-JavaScript libraries like pdf-lib with no browser at all. Playwright runs in Bun too, but it is not officially supported: the request to document Bun as a runtime (microsoft/playwright issue #38095) was closed as not planned in November 2025. This guide shows each working path, the production tradeoffs, and when to skip the browser entirely.

Which PDF approach should you use in Bun?

The right choice depends on whether you need HTML and CSS as your layout language, and whether your host can run a Chromium process. The table below maps each option to its constraint.

ApproachBrowser neededBun statusBest for
Puppeteer + ChromiumYesWorks, community-testedHTML/CSS layouts on a host that can run Chromium
Playwright + ChromiumYesUnofficial, finickyTeams already on Playwright, aware of the risk
pdf-lib (pure JS)NoWorks everywhere Bun runsProgrammatic PDFs, single-file binaries, edge targets
Hosted HTML-to-PDF APINo (remote)Works via fetchHTML/CSS layouts without operating a browser

The "Bun status" column reflects community reports and official issue trackers as of mid-2026. Puppeteer has no official Bun support statement, but works in practice; Playwright's maintainers declined to support Bun explicitly.

The decision axis is not "which renders better." Puppeteer, Playwright, and a hosted API all drive the same Chromium engine and produce the same PDF from the same HTML. The axis is operational: do you want a Chromium binary inside your deployment, and can your runtime spawn it?

Can you run Puppeteer in Bun?

Yes, Puppeteer runs in Bun in most setups. Bun implements a large share of the Node.js APIs Puppeteer depends on, so the standard install and launch flow works without patches. Puppeteer is the most reliable headless-browser path in Bun today.

Install it and download the Chromium build Puppeteer manages:

bun add puppeteer
bunx puppeteer browsers install chrome

Then render HTML to a PDF. Bun has a built-in Bun.write() for the file output, so there is no fs import:

import puppeteer from "puppeteer";
 
const html = `
  <!doctype html>
  <html>
    <body style="font-family: system-ui; padding: 40px">
      <h1>Invoice INV-001</h1>
      <p>Total due: $1,500.00</p>
    </body>
  </html>
`;
 
const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.setContent(html, { waitUntil: "networkidle0" });
  const pdf = await page.pdf({ format: "A4", printBackground: true });
  await Bun.write("invoice.pdf", pdf);
} finally {
  await browser.close();
}

The waitUntil: "networkidle0" option waits until the page has no network activity, so web fonts and remote images finish loading before capture. The finally block matters: every launch() spawns a Chromium process, and skipping close() leaks processes that accumulate until the host runs out of memory.

Does Playwright support Bun?

Playwright does not officially support Bun. The feature request to add and document Bun as a supported runtime, issue #38095, was opened on November 1, 2025 and closed as not planned. An earlier compatibility issue (#27139) tracked the same gap.

Playwright can still run in Bun. A package.json exports condition for Bun was merged, and people do run Playwright under Bun in production. But there are open bugs: Chromium launch fails with the native Bun runtime in some configurations (oven-sh/bun #23826), and bun build --compile does not work with Playwright (oven-sh/bun #27530).

The practical reading: if your team is already standardized on Playwright and you accept that Bun is an untested runtime for it, Playwright's page.pdf() works the same as in Node.js. PDF output in Playwright is Chromium-only, so Firefox and WebKit projects cannot produce PDFs regardless of runtime. If you are starting fresh on Bun and want a headless browser, Puppeteer is the lower-risk choice. For a deeper feature-by-feature view, see Playwright vs Puppeteer for PDF generation.

How do I generate a PDF in Bun without a browser?

Use a pure-JavaScript library. pdf-lib builds and edits PDFs from primitives such as text, shapes, images, and embedded fonts, with no Chromium dependency. It runs anywhere Bun runs, including inside bun build --compile binaries and constrained runtimes that cannot spawn a browser process.

import { PDFDocument, StandardFonts, rgb } from "pdf-lib";
 
const doc = await PDFDocument.create();
const page = doc.addPage([595, 842]); // A4 in points
const font = await doc.embedFont(StandardFonts.Helvetica);
 
page.drawText("Invoice INV-001", {
  x: 50,
  y: 780,
  size: 24,
  font,
  color: rgb(0.07, 0.09, 0.15),
});
page.drawText("Total due: $1,500.00", { x: 50, y: 740, size: 14, font });
 
const bytes = await doc.save();
await Bun.write("invoice.pdf", bytes);

The tradeoff is layout. pdf-lib gives you a coordinate system, not a layout engine, so you position every element by hand and there is no CSS, no flexbox, and no automatic text wrapping. For a fixed badge, label, or stamped form this is fine. For an invoice with dynamic line items or a multi-page report, manual positioning becomes the slow part. That is the reason HTML-to-PDF exists: you describe the document in HTML and CSS and let Chromium handle pagination and wrapping.

Bun vs Node.js for PDF generation

Bun and Node.js produce identical PDFs because both drive the same Chromium engine through the same Puppeteer API. The differences are in startup, tooling, and compatibility, not in the rendered output.

FactorBunNode.js
Dependency installFaster (bun install)Slower (npm install)
Process startupFaster cold startSlower cold start
page.pdf() render timeSame (Chromium-bound)Same (Chromium-bound)
Puppeteer supportCommunity-testedOfficially supported
Playwright supportNot planned (#38095)Officially supported
Single binarybun build --compile, but not with browserspkg / SEA, same browser limit

The headline is that runtime choice does not change render time. A single HTML-to-PDF call is dominated by Chromium loading the page and painting it, which is typically in the hundreds of milliseconds with a warm browser, and that cost is the same on both runtimes. We measured the Chromium-bound part in detail in the HTML to PDF benchmark 2026. Bun's advantage is everything around the render: faster bun install in CI and a faster process start, which helps when you spin up workers on demand.

Where the DIY browser approach breaks in production

A puppeteer.launch() call in a script works on the first try. The problems show up later, under load and in deployment, and they are the same on Bun and Node.js because they come from operating Chromium, not from the runtime.

Three recurring issues:

First, the image size. A container that runs headless Chromium needs the browser binary plus its system libraries (fonts, graphics, and shared objects), which adds roughly 300 MB or more to the image. That inflates build times, registry storage, and cold-start pulls.

Second, concurrency and memory. Each launch() is a separate Chromium process holding tens of megabytes. Under concurrent requests you either pool and reuse browser instances carefully or you watch memory climb until the container is killed. A leaked page or a missing close() turns into an out-of-memory incident.

Third, the runtime ceiling. Constrained platforms that cannot spawn a process or load native binaries cannot run Puppeteer or Playwright at all, on any JavaScript runtime. This is the same wall described in PDF generation on Cloudflare Workers and across serverless environments. On those targets your only in-process option is a pure-JS library, with the manual-layout tradeoff above.

None of this means Puppeteer in Bun is the wrong choice. It means a single page.pdf() line hides an operational tail: a browser to patch for security, processes to supervise, and memory to cap.

Generate a PDF from Bun with a hosted API

If you want HTML and CSS as your layout language but not a Chromium binary in your deployment, call a hosted HTML-to-PDF API over Bun's built-in fetch. There is no browser to install, no process to supervise, and no 300 MB layer. PDF4.dev renders the HTML on the same Chromium engine and returns the PDF.

const res = await fetch("https://pdf4.dev/api/v1/render", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PDF4_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    template_id: "invoice",
    data: { invoice_number: "INV-001", total: "$1,500.00" },
  }),
});
 
await Bun.write("invoice.pdf", await res.arrayBuffer());

This is the same five lines on Bun and Node.js because it is a plain HTTP request: Bun's fetch is the standard Web API, so nothing is runtime-specific. For large documents you can pass delivery: "url" and get back a signed link instead of the bytes, which keeps the response small. You can also test HTML-to-PDF output without writing any code first with the free Html To PdfTry it free tool.

Which approach should you choose?

Pick by your deployment constraint, then by your layout needs.

If your host can run Chromium and you want HTML and CSS, use Puppeteer in Bun. It is the most reliable in-process browser path on Bun, and you own the rendering. Accept the image size, process supervision, and security patching that come with operating a browser.

If you need a single-file binary (bun build --compile) or you deploy to a runtime that cannot spawn a browser, use pdf-lib. You give up CSS layout and position elements manually, but you get a dependency that runs anywhere Bun runs.

If you want HTML and CSS without operating a browser, call a hosted API with Bun's fetch. You trade a network hop for zero Chromium in your deployment. For dynamic invoices, reports, and certificates that change layout with the data, this removes the operational tail while keeping HTML as the template language. See the Node.js guide for the same patterns applied to the wider JavaScript ecosystem.

Avoid Playwright on Bun unless you are already committed to Playwright and accept that Bun is an unsupported runtime for it. The maintainers closed Bun support as not planned, and there are open launch and compile bugs. On Bun specifically, Puppeteer is the safer headless-browser choice.

Free tools mentioned:

Html To PdfTry it free

Start generating PDFs

Build PDF templates with a visual editor. Render them via API from any language in ~300ms.