Convert a Webpage to PDF with Playwright in TypeScript
Generate a webpage PDF with Playwright in TypeScript. Learn how to choose print styles, page size, backgrounds, margins, pagination, and output handling.
Use Playwright’s Chromium browser to open a page, navigate to the URL, and call page.pdf(). PDF generation uses print CSS by default; set printBackground: true to include background graphics. This TypeScript example writes an A4 PDF to disk and closes the browser even if navigation or PDF generation fails:
import { chromium } from 'playwright';
async function main(): Promise<void> {
const browser = await chromium.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.pdf({
path: 'page.pdf',
format: 'A4',
printBackground: true,
});
} finally {
await browser.close();
}
}
main().catch((error: unknown) => {
console.error(error);
process.exitCode = 1;
});
Install Playwright and its Chromium browser before running the script: npm install playwright and npx playwright install chromium. Save the example in a TypeScript file and run it with your chosen TypeScript runner or compile it with your project’s TypeScript setup. See the official Pages guide for page creation and navigation, and the Page API reference for the PDF options.
1. Choose print or screen styles
page.pdf() generates output using print CSS media by default. A site may use different rules for printing: navigation may disappear, columns may reflow, and elements may be hidden. If you want the PDF to reflect the page’s screen styles, emulate screen media before generating it:
await page.emulateMedia({ media: 'screen' });
await page.pdf({ path: 'page.pdf', format: 'A4', printBackground: true });
Choose based on the intended document. Print media is usually appropriate for a document meant to be read or printed on paper; screen media can be useful when you need the site’s on-screen colors and layout. Check the result on pages whose CSS defines explicit @media print or @page rules.
2. Set page size, orientation, and margins
Use a named paper format for a conventional document, or set dimensions when the output needs a custom page geometry. If format is present, it takes precedence over width and height. When the page’s CSS @page declaration should set the paper size, enable preferCSSPageSize.
| Option | What it controls | When to use it |
|---|---|---|
format |
Named paper size such as A4 or Letter |
Standard paper dimensions; overrides width and height |
width, height |
Explicit paper dimensions | Custom page sizes when no format is specified |
preferCSSPageSize |
Whether CSS @page size takes precedence |
The webpage owns its print page geometry |
landscape |
Landscape rather than portrait orientation | Wide tables, diagrams, or landscape reports |
margin |
Top, right, bottom, and left page margins | Room for printed content or headers and footers |
scale |
Scales page content; supported range is 0.1–2 | Fit content or adjust its size carefully |
Dimensions and margins accept px, in, cm, and mm; an unlabelled number is interpreted as pixels. For example:
await page.pdf({
path: 'report.pdf',
width: '11in',
height: '8.5in',
landscape: true,
margin: { top: '0.5in', right: '0.4in', bottom: '0.5in', left: '0.4in' },
scale: 0.95,
});
Avoid specifying conflicting geometry without a reason. Decide whether the API options or the page’s CSS should own the final page size, then inspect a multi-page result for clipping and awkward breaks.
3. Include backgrounds and preserve colors
printBackground defaults to false, so background graphics are omitted unless you enable it. Background graphics and exact CSS color adjustment are separate concerns: PDF rendering adjusts colors for printing by default. If exact CSS colors matter, the Playwright API documentation points to the CSS property -webkit-print-color-adjust.
await page.addStyleTag({
content: `html { -webkit-print-color-adjust: exact; print-color-adjust: exact; }`,
});
await page.pdf({ path: 'colored.pdf', format: 'A4', printBackground: true });
Use this only when preserving the authored colors is important. Check output for readability and ink-heavy backgrounds, especially when the PDF is intended for printing.
4. Control pagination and document details
PDF options can also set page ranges, add header and footer templates, and request an outline or tagged PDF. These options are useful for longer documents or downstream accessibility and navigation needs; use only the ones your output requires.
- Page ranges: Select the pages to include when you do not need the entire document. Confirm that the resulting range matches the page numbering in the generated document.
- Header and footer templates: Add repeated page furniture where needed. Template scripts are not evaluated, and page styles are not visible inside the templates, so keep template markup self-contained.
- Outline and tagged PDF: Enable the corresponding options when the PDF consumer benefits from document navigation or tagging. Review the produced file in the target viewer.
- Scale and margins: Adjust these to address clipping or excessive whitespace; confirm that text remains legible after scaling.
Refer to the official PDF option reference for the current option names and types. PDF support can be browser-specific; the Playwright MCP PDF Export page explicitly scopes its statement that PDF generation is Chromium-only to that MCP tool. Do not infer from that tool page alone a support matrix for every Playwright browser API.
5. Save to a file or use the returned buffer
Supplying path saves the PDF directly. Without a path, page.pdf() returns a Buffer, which your application can store, upload, or return from a service. For example, use a buffer when a web endpoint needs to send the generated PDF without first writing a named file:
const pdfBuffer: Buffer = await page.pdf({ format: 'A4', printBackground: true });
// Pass pdfBuffer to your application's storage or HTTP response code.
Keep browser cleanup in a finally block in either flow. If creating PDFs for multiple URLs, reuse a browser process where appropriate and create a fresh page per job; ensure a failed job does not prevent browser cleanup.
6. Wait for the page you actually need
Navigation completion and application readiness are not always the same. The example waits for networkidle, but pages with ongoing network activity may never reach it. If the content appears after navigation, wait for a specific selector that identifies the finished content, or use a bounded delay when the page gives no better readiness signal.
await page.goto('https://example.com/report', { waitUntil: 'domcontentloaded' });
await page.locator('[data-report-ready="true"]').waitFor({ state: 'visible', timeout: 15_000 });
await page.pdf({ path: 'report.pdf', format: 'A4', printBackground: true });
Pick a readiness condition that matches the target site. A fixed delay can waste time or still be too short; a selector wait is usually more meaningful when the page exposes a stable element. Also consider whether the page requires authentication or cookies, and provide those through the browser context before navigation when your application is authorized to access it.
7. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Backgrounds or colored panels are missing | printBackground is off by default |
Set printBackground: true; separately consider -webkit-print-color-adjust when exact colors are needed. |
| PDF layout differs from the browser screenshot | page.pdf() uses print CSS by default |
Use page.emulateMedia({ media: 'screen' }) before generating the PDF if screen styling is desired. |
| Content is cut off or unexpectedly scaled | Conflicting format and dimensions, margins, CSS @page, or scale |
Choose one source of page sizing; remember format overrides width and height, and use preferCSSPageSize when CSS should control sizing. |
| Navigation or selector wait times out | The target is slow, unreachable, continuously active, or the selector is wrong | Check URL access and selector correctness; prefer a specific readiness selector over unbounded waiting on network quiet. |
| The PDF misses content loaded after the first render | The application fills the page asynchronously | Wait for a page-specific ready selector before calling page.pdf(). |
| PDF generation is unavailable in the chosen browser | PDF capability may depend on browser and API context | Consult the current Page API reference for the API in use. The Chromium-only statement on the MCP PDF Export page applies to that MCP tool. |
| The script exits with a browser launch error | Playwright’s browser binary may not be installed in the environment | Install the required browser with Playwright’s install command and check that the runtime environment permits launching it. |
8. Performance, reliability, and cost
PDF generation requires launching and running a browser, so factor browser startup, navigation, page readiness, and rendering into the latency of a job. Reusing a browser process across jobs can avoid repeatedly starting the browser, while isolating each job in its own page helps keep page state separate. Bound navigation and selector waits, close pages or contexts after use, and always close the browser when the process is finished.
Reliability depends on the target site and its assets as well as your script: remote resources can fail, pages can change their markup, and dynamic content can appear after navigation. Use a readiness condition tied to the content you need, log the URL and failure stage, and retry only errors that are plausibly transient. The PDF is a digital output, so the direct costs to account for are your browser runtime, compute, storage, and any services your own deployment uses; the cited Playwright documentation does not establish a fixed per-PDF price or performance benchmark.
9. Or skip the browser setup
If you need a webpage capture or PDF without managing a Playwright browser, ScreenshotNeo is a website screenshot API and MCP server for developers. Its one-call API can return PNG, JPEG, WebP, or PDF; see the API documentation for request options. For a PDF, add format=pdf to the request:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -d format=pdf -o page.pdf
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com", "format": "pdf"},
timeout=90,
)
r.raise_for_status()
open("page.pdf", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://stripe.com',
format: 'pdf',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`ScreenshotNeo request failed: ${res.status}`);
await import('node:fs/promises').then(({ writeFile }) => writeFile('page.pdf', Buffer.from(await res.arrayBuffer())));
ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots, and every feature is on every plan.
Create a free ScreenshotNeo account and get 1,000 screenshots a month with no card.
10. FAQ
Does the PDF call return bytes I can send directly to a client?
Yes. Without path, the API returns a Buffer; your application can use it in its own response or storage flow.
Can I use my page’s CSS to define paper size?
Yes. Use preferCSSPageSize when the CSS @page size should take precedence over the API page dimensions.
Is the MCP PDF export tool the same thing as calling page.pdf()?
No. The MCP page documents a separate tool and scopes its Chromium-only statement to that tool. Consult the Page API documentation for page.pdf() behavior.
Where can I check current Playwright behavior?
Use the official Page API reference and Pages guide, since option details can change over time.


