How to Generate PDFs and Screenshots with Browser APIs
Choose between a user-driven print dialog and automated browser output. Learn how to create PDFs and screenshots with Puppeteer and Playwright.
Use window.print() when a person should choose a printer or save the current page as a PDF. Use browser automation such as Puppeteer or Playwright when your code must produce PDF bytes, a file, or a screenshot without opening a print dialog. Print CSS controls PDF layout; screenshot APIs capture the rendered page as pixels. These are different output paths.
This guide covers user-initiated printing, automated PDF generation, and screenshots, including print versus screen styling, page geometry, capture scope, loading, errors, and cost considerations.
1. Choose the right browser API
| Need | Use | What you receive |
|---|---|---|
| A person prints or saves the current page | window.print() |
A browser-owned print dialog; the user chooses the destination |
| Code produces a PDF file or buffer | Puppeteer or Playwright page.pdf() |
PDF output, usually using print media styles |
| Code captures a visible page or element as an image | Puppeteer or Playwright screenshot API | Image bytes or a saved image file |
window.print() opens the print dialog for the current document, waits for loading if needed, blocks while the dialog is open, and returns undefined. It is a user-driven print flow, not an API that returns PDF bytes. For programmatic output, run a browser automation process where the browser can access the target page.
Pick based on the interaction model, output type, whether print or screen media should be rendered, screenshot scope, browser environment, and how precisely page dimensions or colors must be controlled. Puppeteer and Playwright behavior depends on the installed framework and browser versions; check the matching API documentation when exact rendering matters.
2. Prepare a page for a user-driven print or Save as PDF
Add a clear button that calls window.print(). Let print CSS remove screen-only controls and adapt content to paper. CSS printing guidance is documented by MDN, and the API behavior is described by MDN’s Window.print() reference.
<button type="button" onclick="window.print()">Print or save as PDF</button>
<main class="document">
<h1>Quarterly report</h1>
<p>This content will be available in the printed document.</p>
</main>
<style>
@page {
size: A4 portrait;
margin: 18mm;
}
@media print {
nav,
button,
.dialog,
.screen-only {
display: none !important;
}
body {
color: #111;
background: #fff;
font: 11pt/1.45 Georgia, serif;
}
a {
color: inherit;
text-decoration: underline;
}
h1, h2 {
break-after: avoid;
}
table, figure {
break-inside: avoid;
}
}
</style>
The @page rule can set page dimensions, orientation, and margins. Print styles can hide navigation or dialogs and adjust typography. Browser print settings and destination remain under the user’s control, so check the result in the browsers your users rely on.
Prefer CSS for ordinary print layout. If content must be changed temporarily, use beforeprint and afterprint to prepare and restore it; MDN recommends @media print where possible. The events can be useful for exceptional cases, but do not treat them as a reliable way to learn whether the user saved a PDF or sent a job to a printer. See MDN’s afterprint event documentation.
3. Generate a PDF with Puppeteer
Install Puppeteer in a Node.js project. Its standard package installs a compatible browser; follow the official installation guide if your environment needs a different browser setup.
npm install puppeteer
Save this as generate-pdf.mjs, then run node generate-pdf.mjs https://example.com. It writes page.pdf in the current directory.
import puppeteer from 'puppeteer';
const url = process.argv[2] ?? 'https://example.com';
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 });
await page.pdf({
path: 'page.pdf',
format: 'A4',
printBackground: true,
margin: { top: '18mm', right: '16mm', bottom: '18mm', left: '16mm' }
});
console.log('Wrote page.pdf');
} finally {
await browser.close();
}
Puppeteer’s page.pdf() uses print CSS by default. When the PDF should use screen styling instead, call await page.emulateMediaType('screen') before page.pdf(). Print color adjustments are applied by default; the documentation describes -webkit-print-color-adjust for cases where exact colors are needed. See the Puppeteer PDF API.
Puppeteer PDF options to consider
format, or explicitwidthandheight, controls paper geometry. Check the installed version’s API for accepted values and precedence.marginsets top, right, bottom, and left margins. CSS@pagecan also set page dimensions and margins; avoid unintentionally specifying conflicting geometry in both CSS and automation options.landscapeselects landscape orientation when using format-based sizing.printBackgroundcontrols whether background graphics are included.pageRangescan restrict output to selected pages where supported by the installed version.- Omit
pathwhen you want the PDF data returned to your program rather than written at that path. Confirm the current API’s output behavior for your version.
For complete option definitions, consult the Puppeteer PDFOptions reference.
4. Generate a PDF with Playwright
Install Playwright and its browser binaries using the official setup instructions. In a Node.js project, the following example uses Chromium.
npm init -y
npm install playwright
npx playwright install chromium
Save as generate-pdf.mjs and run node generate-pdf.mjs https://example.com.
import { chromium } from 'playwright';
const url = process.argv[2] ?? 'https://example.com';
const browser = await chromium.launch({ headless: true });
try {
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle', timeout: 60000 });
await page.pdf({
path: 'page.pdf',
format: 'A4',
printBackground: true,
margin: { top: '18mm', right: '16mm', bottom: '18mm', left: '16mm' }
});
console.log('Wrote page.pdf');
} finally {
await browser.close();
}
Playwright PDF output uses print CSS. To request screen media, call await page.emulateMedia({ media: 'screen' }) before page.pdf(). Its PDF API returns a PDF buffer when no output path is supplied. Consult the Playwright page.pdf() reference for current geometry and output options.
5. Capture screenshots with Puppeteer or Playwright
Use a screenshot API when you need an image file or image bytes. A screenshot is a rendering of pixels, not a PDF page. Decide whether the requirement is the current viewport, a particular element, or the full scrollable page. Full-page screenshots can be very tall, so verify that the downstream format can handle the resulting dimensions.
Puppeteer viewport screenshot
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage({
viewport: { width: 1440, height: 900 },
deviceScaleFactor: 1
});
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'viewport.png', type: 'png' });
await page.locator('main').screenshot({ path: 'main.png' });
} finally {
await browser.close();
}
Puppeteer’s screenshot API supports returning image data as bytes or base64, as well as saving to a path. See Puppeteer Page.screenshot() and the Puppeteer screenshot guide. The device scale factor affects the relationship between CSS pixels and output pixels; confirm dimensions in the installed version and your chosen viewport.
Playwright full-page or element screenshot
import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: true });
try {
const page = await browser.newPage({
viewport: { width: 1440, height: 900 },
deviceScaleFactor: 1
});
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.screenshot({ path: 'viewport.png' });
await page.screenshot({ path: 'full-page.png', fullPage: true });
await page.locator('main').screenshot({ path: 'main.png' });
} finally {
await browser.close();
}
Playwright screenshots show the viewport by default. Set fullPage: true for the full scrollable page. Locator screenshots capture a particular element. The API documents screenshot options including output type and full-page capture in the Playwright Page.screenshot() reference.
For image output, check the framework version’s supported image types and options. Define the viewport and device scale factor explicitly when stable dimensions matter. A capture of a responsive page at a mobile viewport is a different rendering from a desktop capture; specify both viewport and device scale instead of relying on defaults.
6. Make automated captures deterministic
- Choose a readiness condition.
DOMContentLoadedis appropriate when you need the initial document structure. A load event waits for page resources. Network-idle conditions can be useful for static pages but may never occur on pages with polling or persistent connections. - Wait for app-specific content. For client-rendered pages, wait for a meaningful selector or state rather than assuming navigation means the application has finished rendering.
- Handle lazy content. Full-page capture does not guarantee that every lazy image or below-the-fold component has loaded. Scroll or trigger the content as appropriate, then wait for the relevant images or selectors.
- Set rendering inputs explicitly. Fix viewport, device scale, media type, locale or other relevant page settings if the output must be reproducible.
- Use print styles for PDFs. PDFs paginate content and may reflow it; screenshots preserve a pixel view. Test page breaks, long tables, and charts in the final output.
- Close browser resources reliably. Put browser shutdown in a
finallyblock so errors do not leave browser processes running.
Do not treat a generic network-idle signal as proof that every image, font, animation, or application request is settled. Prefer an app-specific readiness condition when you control the page. For externally hosted pages, choose a sensible timeout and make the capture result observable to the caller.
7. Common problems and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
window.print() does not produce a file in JavaScript |
It opens the browser print dialog and returns no PDF bytes | Use it for a user-driven flow; use Puppeteer or Playwright page.pdf() for programmatic output. |
| PDF looks unlike the screen | PDF generation uses print media by default | Adjust @media print styles, or emulate screen media before calling PDF generation if that is the desired layout. |
| PDF backgrounds or colors are missing | Background printing may be disabled or print color adjustment changes colors | Enable the framework’s background printing option and inspect print color adjustments; consult its current PDF documentation. |
| Unexpected page size or margins | CSS @page, PDF options, or print dialog settings specify different geometry |
Choose one intended paper size and margin policy; inspect CSS and API options together. For window.print(), the user controls the dialog settings. |
| Screenshot is only the visible portion | Viewport screenshots are the default | Use Playwright’s fullPage: true or the corresponding full-page option in the installed framework. |
| Images or app content are missing | The page was captured before relevant content finished loading, or content is lazy-loaded | Wait for a page-specific selector or readiness state and trigger lazy content before capture. |
| Navigation times out on a live application | Persistent requests can prevent a network-idle condition | Wait for a more suitable navigation event and then for the specific content you need. Keep a finite timeout. |
| Browser launch fails in a container | Browser binaries or operating-system dependencies are missing, or the executable differs from the expected version | Install the browser and dependencies using the framework’s official setup instructions; align the browser and library versions. |
| Screenshot output dimensions differ between runs | Viewport or device scale factor is implicit, or page layout is responsive | Set viewport and device scale explicitly and keep the rendering environment consistent. |
8. Performance, reliability, and cost
Each automated capture requires browser work: navigation, resource loading, layout, rendering, and output encoding. Large pages, full-page images, PDFs with many pages, and pages that wait on slow third-party resources can take longer and use more memory. Reuse a browser process across jobs when your service design allows it, but isolate page state and close pages when work finishes. Set navigation and job timeouts, and do not let a single unresponsive target hold a worker indefinitely.
For reliability, capture explicit status and errors, retry only transient failures, and avoid blindly repeating expensive or state-changing page workflows. Keep the browser version aligned with your automation package. Rendering may differ across browser engines and versions; validate PDFs and images in the environment that will generate them. Puppeteer documents supported browser versions in its supported browsers guide.
Self-hosted automation costs include the compute and operational work for browser processes, dependencies, queues, storage, and retries. A user-driven print dialog shifts file destination and printer choice to the person. A hosted screenshot API instead charges according to its own plan and billing rules; compare required volume, output formats, privacy needs, retries, and the time needed to operate browsers. Do not infer capture success from an HTTP response alone when the service exposes explicit result metadata.
9. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request can return a PNG, JPEG, WebP, or PDF. Its documented API options include full-page capture with lazy images loaded, CSS element capture, custom viewport and device presets, PDF settings, custom CSS and JavaScript, waits, headers and cookies, caching, signed links, asynchronous jobs, and bulk capture. See the ScreenshotNeo API documentation for parameters and response handling.
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,
)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
Cookie and consent banners are accepted like a visitor and removed along with 60+ known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and responses report page verdict and billing through headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 shots a month with no card; paid plans start at $5 for 3,000 shots.
Sign up for 1,000 free screenshots a month, with no card required.
10. FAQ
Can browser JavaScript silently save a PDF from window.print()?
No. The browser owns the print dialog and the user’s destination choice. Use automation when code must receive a PDF file or bytes.
Should I use PDF or a full-page screenshot for a report?
Use PDF when the content should paginate and print well. Use a full-page screenshot when you need a visual image of the rendered page. They preserve different properties and are not interchangeable.
Can I print a page with a hidden iframe?
MDN documents using an iframe to print an external page, but this still invokes a user-facing browser print flow. It does not turn window.print() into a byte-returning API.
Does BrowserStack support Puppeteer?
BrowserStack documents support for Puppeteer and Playwright testing. It is an optional hosted testing service, rather than the browser capture API itself; see its Puppeteer documentation.


