To turn an Express page into a PDF, add a route that launches Puppeteer, opens the page you want to print, waits until it is ready, calls page.pdf(), and sends the resulting bytes or saves them to a file. Put browser cleanup in a finally block so Chromium is closed even when navigation or PDF generation fails.
Render an Express page as a PDF
This example adds a GET /report endpoint that visits a separate Express view at /report-view, generates an A4 PDF, and returns it to the caller. It assumes Node.js can run a Chromium browser installed for Puppeteer and that the view route serves the content to print.
import express from 'express';
import puppeteer from 'puppeteer';
const app = express();
app.get('/report', async (req, res, next) => {
let browser;
try {
browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('http://localhost:3000/report-view', {
waitUntil: 'networkidle2',
});
const pdf = await page.pdf({
format: 'A4',
printBackground: true,
preferCSSPageSize: true,
});
res.type('application/pdf').send(Buffer.from(pdf));
} catch (error) {
next(error);
} finally {
if (browser) await browser.close();
}
});
app.listen(3000);
Express routes match an HTTP method and path; in this example app.get() handles requests for /report. The handler passes errors to Express with next(error), while finally closes the browser whether the route succeeds or throws. Puppeteer’s documented PDF flow likewise launches a browser, creates a page, navigates to a URL, calls page.pdf(), and closes the browser. Puppeteer PDF generation guide · Express routing guide.
Choose the page to print
The URL passed to page.goto() determines what is rendered. Keeping a dedicated view route such as /report-view separate from the PDF endpoint can help avoid a route loading itself recursively. In a deployed app, use the reachable origin and path for the view; localhost:3000 is only appropriate when Chromium runs where that server is listening.
#1 Best Overall
Send PDF bytes or write a file
Without a path option, page.pdf() returns PDF bytes, which the example sends with an application/pdf content type. If the goal is a file artifact on the server rather than an HTTP response, set path in the PDF options. Ensure that the destination directory exists and that the process has permission to write there.
Wait for the page to be ready before printing
page.goto() must finish before PDF generation, but a navigation event alone does not guarantee that an app has finished fetching its data or completing client-side rendering. The example uses waitUntil: 'networkidle2', a wait condition used by Puppeteer’s official example. For pages with delayed content, wait for an app-specific signal as well:
Rank #2
await page.goto('http://localhost:3000/report-view', {
waitUntil: 'networkidle2',
});
await page.waitForSelector('[data-report-ready="true"]');
const pdf = await page.pdf({ format: 'A4' });
The selector is illustrative: render it only after the data and visible content required in the document are ready. Another option is for the application to set a known flag, then wait for that flag with Puppeteer before printing. page.pdf() waits for document fonts by default, so an extra font wait is usually unnecessary unless your own readiness requirements call for it. Puppeteer PDF generation guide.
Do not rely blindly on network idle
Network activity may continue because of polling, analytics, or long-lived connections, while a page may become quiet before its data is actually displayed. If the wait times out or a PDF is missing content, use a selector or application-set readiness flag that represents the actual printable state. Set a realistic navigation timeout for the page and handle a timeout as a failed render rather than returning a partial document without telling the caller.
Why the PDF can look different from the browser
Puppeteer generates PDFs using the print CSS media type by default. Print-specific styles, page breaks, and print color behavior can therefore make output differ from the interactive screen. If the intended design is the screen layout, emulate the screen media type before generating the PDF:
await page.emulateMediaType('screen');
const pdf = await page.pdf({ printBackground: true });
Background graphics are disabled by default; set printBackground: true when backgrounds matter to the design. Print colors may also be adjusted by the browser. For closer color fidelity, CSS can use -webkit-print-color-adjust: exact on the relevant elements. Check the result in the generated PDF, since exact color adjustment does not resolve differences caused by page size, scaling, or print styles. Puppeteer media type API · Puppeteer PDF options.
Rank #4
Set page size, margins, orientation, and output options
The settings below are documented options for page.pdf(). The documented default format is Letter; other defaults are noted where relevant. Puppeteer PDF options.
| Option | What it controls | Practical use |
|---|---|---|
format |
Paper format; documented default is letter. |
Use 'A4' when the output should use A4 paper. |
preferCSSPageSize |
Whether CSS @page size takes priority over format, width, or height. |
Set it when the document’s own page rule should choose the paper size. |
landscape |
Orientation; defaults to false. |
Set true for a wide report or table. |
margin |
Print margins. | Set margins explicitly when content must align consistently with a page design. |
pageRanges |
Pages to include, such as '1-5, 8, 11-13'. |
Print selected pages from a longer document. |
printBackground |
Whether to include backgrounds; defaults to false. |
Enable when backgrounds, colored panels, or background graphics carry information. |
scale |
Scale from 0.1 to 2; defaults to 1. |
Adjust only when the layout needs a controlled size change. |
timeout |
PDF operation timeout; defaults to 30,000 ms. | Increase for large or complex documents when the normal limit is insufficient. |
waitForFonts |
Whether PDF generation waits for document.fonts.ready; defaults to true. |
Leave enabled when the output depends on web fonts. |
path |
File destination; if omitted, the method returns PDF bytes. | Use when writing a server-side PDF rather than returning bytes. |
Coordinate CSS and PDF sizing
For predictable pagination, define print rules in the page’s CSS and choose whether those rules or Puppeteer’s explicit format should control paper size. When preferCSSPageSize is true, CSS @page sizing takes priority. Avoid trying to fix a layout by changing several independent controls at once: first verify the page size, then margins and orientation, then scale and page breaks.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Clear out junk files and repair common Windows errors3Fix the driver behind crashes, sound loss and screen glitchesBest Value
- Used Book in Good Condition
Handle errors, browser lifecycle, and production load
Chromium is a separate browser process, so a render endpoint should treat launch, navigation, and PDF generation as fallible operations. The try/catch/finally pattern prevents errors from becoming unhandled and closes the browser after each request. In a production service, also decide how the endpoint behaves when rendering fails: return an error response through Express error middleware, log a useful request identifier and failure stage, and avoid sending a success content type until the PDF exists.
- Cleanup: retain the browser reference and close it in
finally, including after timeouts. - Resource use: each launch and page consumes server resources. Under concurrent traffic, set application-level concurrency limits and monitor memory and process usage rather than allowing unbounded browser launches.
- Timeouts: navigation waits and PDF generation have separate failure points. Configure and report them deliberately; do not assume a PDF timeout means navigation failed.
- Repeat work: if the same report is requested repeatedly, consider whether caching the generated artifact fits the freshness and privacy requirements of the data.
- Deployment: the host must allow the Chromium process and provide the dependencies Puppeteer needs. A local development success does not establish that a restricted serverless or container environment can launch Chromium.
Troubleshoot common Puppeteer PDF failures
| Symptom | Likely cause | What to check or change |
|---|---|---|
| PDF is blank or missing report data | Printing began before client-side data or rendering finished. | Wait for a selector or application readiness flag tied to the actual report content. |
| Navigation hangs or times out | The page never reaches the selected wait condition, or the target URL is unreachable from Chromium. | Verify the URL from the server environment; use an appropriate readiness condition and review long-lived network activity. |
| Colors or background panels are absent | PDF uses print media and background graphics are off by default. | Set printBackground: true; review print CSS and consider -webkit-print-color-adjust: exact. |
| PDF layout differs from the browser | Print CSS is active, or page size and margins differ from the screen viewport. | Use page.emulateMediaType('screen') for screen styles, or refine print styles and PDF page settings. |
| Wrong paper size or clipped content | CSS @page, format, margins, orientation, or scale conflict. |
Choose which sizing source is authoritative; check preferCSSPageSize, margins, landscape, and scale. |
| Browser processes remain after failures | Browser closure is missing from an error path. | Close the browser in finally, guarded when launch itself fails. |
| Web font appears late or is replaced | The font is unavailable to the page or did not load as expected. | Check font requests and CSS; Puppeteer’s PDF operation waits for document.fonts.ready by default. |
Or skip the browser setup
If you need a screenshot or PDF rather than a Puppeteer-controlled Express workflow, ScreenshotNeo offers a website screenshot API and MCP server. Its one-call request can return a PNG, JPEG, WebP, or PDF. The endpoint and parameters are documented at ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots. Sign up free for 1,000 screenshots a month, no card required.
Frequently Asked Questions
Can I return the PDF directly from an Express route?
Yes. Send the bytes returned by `page.pdf()` with the `application/pdf` content type, as in the example.
Free tools Windows power users keep installed
One-click scans. No signup required.
Does Puppeteer wait for fonts before creating the PDF?
Yes. `waitForFonts` defaults to `true` and waits for `document.fonts.ready`.
How do I print only certain pages?
Set `pageRanges`, for example `1-5, 8, 11-13`, in the `page.pdf()` options.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

