Puppeteer HTML to PDF Example
Generate PDFs from HTML with Puppeteer, control print CSS, paper size, fonts, backgrounds, page ranges, and troubleshoot common rendering issues.
Puppeteer HTML to PDF Example
The basic pattern is: launch Puppeteer, create a page, load HTML with page.setContent() or navigate with page.goto(), then call page.pdf(). Close the browser in a finally block so failures do not leave a process running.
Puppeteer generates PDFs with the print CSS media type by default. Set the media type, paper format, margins, background printing, and CSS page-size behavior deliberately because each affects the final document. See the Puppeteer PDF generation guide and the Page.pdf() API reference.
Minimal HTML-to-PDF example
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setContent('<main><h1>Hello, PDF</h1></main>');
await page.pdf({
path: 'output.pdf',
format: 'A4',
printBackground: true
});
} finally {
await browser.close();
}
Install Puppeteer with npm install puppeteer. The package downloads a compatible browser during installation. Run the file as an ES module, or use the equivalent CommonJS import in a project configured for CommonJS.
Generate a PDF from a URL
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com', {
waitUntil: 'networkidle0',
timeout: 60000
});
await page.pdf({
path: 'example.pdf',
format: 'A4',
printBackground: true
});
} finally {
await browser.close();
}
Use page.goto() when the HTML is served by a website. Use page.setContent() when your application already has the markup as a string. The setContent() API accepts HTML and wait options.
Control print and screen styles
page.pdf() uses the print media type. If the document should look like its screen layout, switch media before generating the PDF:
await page.emulateMediaType('screen');
await page.pdf({
path: 'screen-styled.pdf',
format: 'A4',
printBackground: true
});
Keep print-specific rules in a stylesheet when the PDF is a document:
@media print {
.site-navigation,
.cookie-banner,
.print-button {
display: none;
}
}
@page {
size: A4;
margin: 18mm 16mm;
}
body {
-webkit-print-color-adjust: exact;
}
Chromium can modify colors for printing. The -webkit-print-color-adjust property asks it to preserve the specified colors. Background graphics are disabled unless printBackground: true is set.
Important PDF options
| Option | What it controls | Default or detail |
|---|---|---|
path |
Writes the generated PDF to a file. | Omit it to receive a buffer. |
format |
Paper preset such as A4 or Letter. |
Letter by default. When set, it takes priority over width and height. |
width, height |
Custom paper dimensions. | Use when a preset is insufficient. |
landscape |
Rotates the paper orientation. | False by default. |
margin |
Top, right, bottom, and left margins. | Specify each side when predictable layout is required. |
printBackground |
Includes CSS background colors and images. | False by default. |
scale |
Scales page content. | 1 by default; documented range is 0.1 to 2. |
pageRanges |
Exports selected pages. | Useful for extracting a subset of a long document. |
preferCSSPageSize |
Chooses CSS @page size over API dimensions. |
False by default. |
waitForFonts |
Waits for fonts before rendering. | True by default. |
timeout |
Limits PDF generation time. | Adjust for large or font-heavy documents. |
The complete option list is in Puppeteer’s PDFOptions reference.
Paper size: A4 or Letter
A4 is 210 × 297 mm (8.2677 × 11.6929 inches). Letter is 8.5 × 11 inches (21.59 × 27.94 cm). Pick the format required by the people or systems receiving the document. If your CSS defines an @page size, set preferCSSPageSize: true so that CSS size wins.
await page.pdf({
path: 'letter-landscape.pdf',
format: 'Letter',
landscape: true,
margin: {
top: '12mm',
right: '12mm',
bottom: '12mm',
left: '12mm'
},
printBackground: true
});
Wait for content, images, and fonts
Navigation completion does not guarantee that application data, images, or web fonts are ready. Choose a wait strategy that matches the page:
await page.goto(url, { waitUntil: 'networkidle0' });
await page.waitForSelector('#report-ready');
await page.evaluate(() => document.fonts.ready);
await page.pdf({ path: 'report.pdf', format: 'A4', printBackground: true });
waitForFonts is true by default. Puppeteer’s documentation notes that bringing a background page to the foreground can be necessary for font readiness. For client-rendered applications, expose a stable marker such as #report-ready after data and charts have rendered. Avoid relying on a fixed sleep unless the page has no better readiness signal.
Return a PDF buffer from a server
import express from 'express';
import puppeteer from 'puppeteer';
const app = express();
app.use(express.json({ limit: '1mb' }));
app.post('/pdf', async (req, res) => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setContent(req.body.html, { waitUntil: 'networkidle0' });
const pdf = await page.pdf({
format: 'A4',
printBackground: true,
preferCSSPageSize: true
});
res.type('application/pdf').send(pdf);
} catch (error) {
res.status(500).json({ error: 'PDF generation failed' });
} finally {
await browser.close();
}
});
app.listen(3000);
Validate or sanitize HTML supplied by callers, restrict navigation targets when URLs are accepted, and enforce request and document size limits in production.
Common problems and fixes
| Symptom | Cause | Fix |
|---|---|---|
| Colors or backgrounds are missing | Background printing is off, or print color adjustment changed the palette. | Set printBackground: true and use -webkit-print-color-adjust: exact. |
| The PDF looks different from the browser | PDF rendering uses print media. | Call page.emulateMediaType('screen'), or add explicit @media print rules. |
| Content is cut off | Paper size, margins, or fixed-height CSS do not match the content. | Use the intended format, review margins, remove fixed heights, and test page breaks. |
CSS @page size is ignored |
API format or dimensions take precedence. | Set preferCSSPageSize: true. |
| Web fonts are absent or fall back | Fonts were not ready when capture began, or the page was backgrounded. | Await document.fonts.ready, keep waitForFonts: true, and investigate font loading or page activation. |
| Charts or data are blank | Client-side rendering has not completed. | Wait for a selector or application readiness marker after data rendering. |
goto times out |
Slow resources, blocked requests, or a page that never becomes idle. | Raise the navigation timeout, choose a less strict waitUntil, and add an explicit readiness check. |
| Browser fails to launch in a container | Missing system libraries or sandbox restrictions. | Use the browser and container setup recommended by your deployment environment; only change sandbox settings when your security model permits it. |
| PDF generation times out | Very large DOM, images, fonts, or complex layout. | Reduce document size, wait for required assets only, and adjust the PDF timeout. |
Performance, reliability, and cost considerations
- Reuse a browser process for multiple jobs, while creating a fresh page per job. Always close pages and browsers in cleanup code.
- Set navigation and PDF timeouts explicitly. A readiness selector is usually more predictable than waiting for every third-party request to become idle.
- Reduce image dimensions and unnecessary scripts before rendering. Large pages consume more memory and take longer to paginate.
- Control external requests when documents load analytics, ads, or other third-party resources. Uncontrolled requests make output and timing less deterministic.
- For repeatable output, pin your Puppeteer version, use stable fonts, and define paper size and margins explicitly.
- PDF generation itself has no Puppeteer service fee, but your application pays for compute, browser memory, storage, and any hosted browser infrastructure.
Or skip the browser setup
ScreenshotNeo provides a website capture API that can return PNG, JPEG, WebP, or PDF output. One request handles browser rendering and PDF options such as paper size, margins, landscape mode, and page ranges. Read the ScreenshotNeo API documentation for the full parameter list.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Before capture, ScreenshotNeo accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers identify the page verdict and whether the request was billed. It also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month with no card, and paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
FAQ
Can Puppeteer convert an HTML string without hosting it?
Yes. Create a page, call page.setContent(html), wait for required assets, and call page.pdf().
How do I make the PDF use screen CSS?
Call page.emulateMediaType('screen') before page.pdf().
Why is my CSS page size ignored?
Set preferCSSPageSize: true; otherwise an API format, width, or height can take precedence.
How do I include background colors?
Set printBackground: true and consider -webkit-print-color-adjust: exact for color fidelity.
Should I use A4 or Letter?
Use the paper standard required by your audience or downstream workflow. A4 and Letter have different dimensions, so define the choice explicitly.


