How to Capture a Page Screenshot with Puppeteer and Save It as a PDF
Use Puppeteer’s screenshot API for image files and its PDF API for documents. Learn how to choose the output, control page styling, and fix common rendering issues.
Use page.screenshot() to save a page as an image, and page.pdf() to save it as a PDF. They are separate Puppeteer APIs: a screenshot is a raster image of the rendered page, while a PDF is a document rendered with print CSS by default. The example below creates both files so you can choose the output that fits your use case.
1. Install Puppeteer and run the capture
Install Puppeteer in a Node.js project, save the script as capture.mjs, and run it with node capture.mjs. Puppeteer launches a browser, navigates to the page, writes page.png and page.pdf, then closes the browser even if capture fails.
npm install puppeteer
import puppeteer from 'puppeteer';
const url = 'https://example.com';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle2' });
// Image output: a full-page PNG.
await page.screenshot({ path: 'page.png', fullPage: true });
// Document output: a PDF, rendered using print CSS by default.
await page.pdf({ path: 'page.pdf' });
} finally {
await browser.close();
}
This is a starting point, not a universal readiness recipe. Some applications keep network requests open or render important content after navigation. Choose a readiness condition that matches the page, as explained below. See the Puppeteer screenshot guide, screenshot API, PDF guide, and PDF API.
2. Choose between a screenshot and a PDF
| Need | Use | Result |
|---|---|---|
| A preview, thumbnail, or image for a report | page.screenshot() |
PNG, JPEG, or WebP image bytes or a file, depending on options and output path |
| A document to read, print, or share across pages | page.pdf() |
PDF document using print media by default |
| Both a visual image and a document | Call both methods | Two different output files |
A screenshot is not a way to make a PDF: setting a screenshot filename to .pdf does not invoke PDF generation. Likewise, page.pdf() does not produce a PNG. Choose the method that matches the file you need.
3. Control screenshot scope and output
By default, a screenshot captures the current viewport. Set fullPage: true to capture the full page, including content below the fold. Use clip when you need a specific rectangle. The path option writes the image to disk; the filename extension can determine the image type.
// Viewport image
await page.screenshot({ path: 'viewport.png' });
// Full-page image
await page.screenshot({ path: 'full-page.png', fullPage: true });
// A rectangular region in CSS pixels
await page.screenshot({
path: 'region.png',
clip: { x: 0, y: 0, width: 900, height: 600 }
});
Use a viewport screenshot when the visible fold is the subject. Use a full-page screenshot for a long-page visual record, bearing in mind that the result can be very tall. Use a clip for a component or region whose position and dimensions you know. Refer to the ScreenshotOptions API for the complete current option list supported by your installed Puppeteer version.
4. Control PDF media, layout, and color
page.pdf() renders with print CSS by default. A site may therefore hide navigation, change columns, or apply print-specific colors compared with the screen view. If you specifically need the screen stylesheet in the PDF, emulate screen media before generating it.
// Use the screen stylesheet for PDF rendering
await page.emulateMediaType('screen');
await page.pdf({ path: 'screen-style.pdf' });
For print output, keep the default print media and define print rules in the page’s CSS where you control the site. PDF color adjustment can also change colors for printing. The documented CSS mechanism to request exact colors is -webkit-print-color-adjust; apply it in the page’s print styles when preserving color matters.
@media print {
html {
-webkit-print-color-adjust: exact;
}
}
The PDF guide documents waiting for fonts by default. If the result still has missing or late content, check whether the page’s own scripts or external resources have finished rendering before calling page.pdf(). Consult the PDF generation guide for the PDF options available in the version you use.
5. Wait for the page content you need
Navigation completion and application readiness are not always the same thing. networkidle2 is used in the sample, but pages with analytics, polling, or persistent connections may not reach a quiet network state. Conversely, a page can reach a network condition before a client-side component has displayed its final content.
For a page with a known readiness marker, wait for that selector explicitly:
await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.waitForSelector('[data-page-ready="true"]');
await page.screenshot({ path: 'page.png', fullPage: true });
Replace the selector with a marker that actually indicates the needed content is ready. If there is no reliable marker, a short delay can be used as a fallback, but it is less dependable than waiting for a meaningful page condition. Avoid choosing a wait condition just because it works for one URL; use the condition that matches the site and capture goal.
6. Run Puppeteer reliably in a script or service
- Always close the browser. Put
browser.close()in afinallyblock so exceptions do not leave browser processes running. - Use an explicit URL and readiness rule. A redirect, slow resource, or client-side render can affect the result.
- Keep outputs distinct. Give the image and PDF different extensions and paths.
- Watch output size. Full-page screenshots of long pages can consume more memory and produce larger files than viewport captures.
- Match the target environment. Browser availability and launch configuration depend on where the script runs; follow Puppeteer’s setup guidance for that environment.
For one-off local captures, launching and closing a browser per script is simple. For a service that captures many pages, browser startup and resource use affect throughput; manage concurrency deliberately and close pages and browsers when work finishes. The research sources do not establish a universal throughput or cost benchmark, so measure against your own pages and runtime.
7. Troubleshoot common output problems
| Symptom | Likely cause | Fix |
|---|---|---|
| The file is an image when you expected a PDF | The script called page.screenshot(). |
Call page.pdf({ path: 'page.pdf' }). Screenshot and PDF output use different APIs. |
| The PDF layout differs from the browser view | PDF generation uses print CSS by default. | Use print styles intentionally, or call page.emulateMediaType('screen') before generating the PDF. |
| PDF colors look faded or different | Print color handling changed the rendered colors. | Set -webkit-print-color-adjust: exact in the relevant page CSS when exact colors are required. |
| Below-the-fold content is missing from the image | The screenshot captured only the viewport. | Set fullPage: true, or use a clip if only a known region is needed. |
| The screenshot or PDF shows a loading state | Navigation completed before the application rendered the desired content, or the selected wait condition did not match the page. | Wait for a page-specific selector or readiness condition before capture. |
| The script hangs waiting for network idle | The page may keep requests open or continuously send traffic. | Choose a different navigation readiness condition and wait for the specific content needed. |
| Browser processes remain after an error | Cleanup did not run when capture threw. | Put await browser.close() in a finally block. |
| PDF text or fonts render unexpectedly | Fonts or page content may not have loaded as expected. | Check font loading and application readiness. Puppeteer’s PDF generation waits for fonts by default; verify the behavior for your installed version. |
8. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request can return a screenshot as PNG, JPEG, or WebP, or a PDF. For the image request below, replace the target URL with your page and provide your API key. See the ScreenshotNeo API documentation for request details.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com \
-o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
timeout=90,
)
r.raise_for_status()
with open("shot.webp", "wb") as f:
f.write(r.content)
Node.js
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers say the page verdict and billing status. Its MCP server lets AI agents use screenshot, page information, and PDF capture tools. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card required.
9. Frequently asked questions
Can Puppeteer save a screenshot directly as a PDF?
No. Use page.screenshot() for an image and page.pdf() for a PDF document.
Does fullPage: true affect PDF output?
No. It is a screenshot option. PDF output is controlled by page.pdf() and its PDF settings.
Why does my PDF look different from the page on screen?
PDF generation uses print CSS by default. Emulate screen media before generating the PDF if screen styling is what you need.
Does Puppeteer wait for fonts before creating a PDF?
The official PDF guide says PDF generation waits for fonts by default. Page-specific scripts and other content may still need their own readiness condition.


