Launch a headless Chromium instance: `const browser = await puppeteer.launch()`, then open a new page with `const page = await browser.newPage()`.
Navigate to the target URL or load HTML directly: `await page.goto('https://example.com', { waitUntil: 'networkidle0' })` — `networkidle0` waits until there are no in-flight network requests, ensuring dynamic content is rendered.
Call `await page.pdf({ path: 'output.pdf', format: 'A4', printBackground: true })` — `printBackground` is required to include CSS background colours and images in the output.
To control margins and headers/footers, pass `margin`, `displayHeaderFooter`, and `headerTemplate`/`footerTemplate` options — templates are HTML strings with special CSS classes for page number and total pages.
Close the browser with `await browser.close()` to release the Chromium process.
Known gotchas
`page.pdf()` ignores media-query styles for `screen` by default because it emulates the `print` media type. If your page looks correct on screen but wrong in the PDF, add `await page.emulateMediaType('screen')` before calling `pdf()`.
Web fonts loaded from external CDNs may not appear in the PDF if Puppeteer's network timeout is too short or the CDN is unreachable. Prefer embedding fonts via `@font-face` with local file paths, or call `page.waitForFunction` to confirm fonts are loaded before generating.
`page.pdf()` is only available in headless mode. Calling it in headful mode throws an error — if you need to debug the rendered page visually, switch to headless before the `pdf()` call.
Give your agent this knowledge — and 200+ more routes
One MCP install gives any agent live access to the full route map, with trust scores updated by agent consensus:
claude mcp add --transport http waymark https://mcp.waymark.network/mcp