How to Print a Webpage With JavaScript or a Screenshot API
Use window.print() for interactive printing, headless browsers for automated PDFs, or a screenshot API when you need reliable capture without browser infrastructure.
Use window.print() when a person is choosing a printer or saving a page manually. Use print CSS to control paper layout. For unattended output, use a headless browser such as Puppeteer or Playwright, or call a hosted screenshot API when you do not want to operate browser workers.
1. Print a page from the browser with JavaScript
The simplest implementation is a button that calls window.print(). MDN describes this method as opening the browser’s print dialog. If the document is still loading, the browser finishes loading before printing, and the method blocks while the dialog is open.
<button type="button" id="print-page">Print this page</button>
<script>
document.querySelector('#print-page').addEventListener('click', () => {
window.print();
});
</script>
See the MDN window.print() reference for the browser behavior.
Wait for JavaScript content before printing
If your page renders data asynchronously, enable the print button only after the required content exists. A loading check prevents a user from printing an empty shell.
<button type="button" id="print-page" disabled>Loading…</button>
<main id="report"></main>
<script type="module">
const button = document.querySelector('#print-page');
const report = document.querySelector('#report');
async function loadReport() {
const response = await fetch('/api/report');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
report.innerHTML = `<h1>${data.title}</h1><p>${data.summary}</p>`;
button.disabled = false;
button.textContent = 'Print this page';
}
loadReport().catch((error) => {
console.error(error);
button.textContent = 'Report failed to load';
});
button.addEventListener('click', () => window.print());
</script>
For user supplied values, create elements with textContent instead of interpolating untrusted HTML.
2. Control the paper output with print CSS
The print media type applies styles only when the browser prints or creates a print-style PDF. The MDN printing guide documents @media print, print stylesheets, and @page.
<link rel="stylesheet" href="print.css" media="print">
/* print.css */
@page {
size: A4;
margin: 16mm;
}
@media print {
nav,
header,
footer,
.screen-only,
.cookie-banner,
.chat-widget,
button {
display: none !important;
}
.print-only {
display: block !important;
}
a {
color: #000;
text-decoration: none;
}
a[href^="http"]::after {
content: " (" attr(href) ")";
overflow-wrap: anywhere;
}
h1, h2, h3 {
break-after: avoid;
}
table, figure, pre {
break-inside: avoid;
}
img {
max-width: 100%;
}
* {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}
.print-only {
display: none;
}
Use explicit screen-only and print-only classes so a print rule does not accidentally remove important information. Page breaks can be guided with break-before, break-after, and break-inside, although the final result still depends on the browser’s pagination.
Temporarily change the interface while printing
window.addEventListener('beforeprint', () => {
document.body.classList.add('is-printing');
});
window.addEventListener('afterprint', () => {
document.body.classList.remove('is-printing');
});
@media print {
.is-printing .live-chart {
display: none;
}
.is-printing .chart-for-print {
display: block;
}
}
These events are useful for swapping a live canvas, expanding collapsed sections, or removing transient controls. Do not rely on them as the only way to include essential content; print CSS should provide a usable fallback.
3. Print without opening a dialog: Puppeteer
For a server job, launch a headless browser, load the URL, wait for the page state you need, and call page.pdf(). Puppeteer generates PDF output using the print CSS media type by default. To use screen styles instead, call page.emulateMediaType('screen') first. The official Puppeteer PDF documentation lists the PDF options.
npm install puppeteer
// print-page.mjs
import puppeteer from 'puppeteer';
const url = process.argv[2] ?? 'https://example.com';
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
try {
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
await page.goto(url, { waitUntil: 'networkidle0', timeout: 60_000 });
await page.evaluate(() => document.fonts?.ready);
await page.pdf({
path: 'page.pdf',
format: 'A4',
printBackground: true,
margin: { top: '16mm', right: '16mm', bottom: '16mm', left: '16mm' }
});
} finally {
await browser.close();
}
networkidle0 is useful for pages that settle after loading, but it can wait indefinitely on analytics or long polling. In that case, wait for a meaningful selector and use a bounded timeout instead.
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60_000 });
await page.waitForSelector('#report-ready', { timeout: 30_000 });
await page.pdf({ path: 'page.pdf', format: 'A4', printBackground: true });
PDF options that affect the result
| Option | Use |
|---|---|
format |
Preset paper size such as A4 or Letter. |
width, height |
Custom paper dimensions. |
margin |
Top, right, bottom, and left page margins. |
landscape |
Rotate the paper orientation. |
printBackground |
Include background colors and images. |
displayHeaderFooter |
Add browser-generated header and footer templates. |
pageRanges |
Export selected pages after pagination. |
Exact colors may require -webkit-print-color-adjust: exact. Fonts must be loaded before PDF generation or the layout can shift when a fallback font is replaced.
4. Capture an image with Puppeteer or Playwright
A PDF represents paginated paper. A screenshot represents the rendered appearance. Puppeteer can return a PNG, JPEG, or WebP-style binary depending on the screenshot options.
const image = await page.screenshot({
path: 'page.png',
fullPage: true,
type: 'png'
});
For Playwright, the corresponding calls are page.pdf() and page.screenshot(). Playwright can also apply a stylesheet during capture, which helps hide dynamic elements and keep repeated captures consistent. Consult the Playwright screenshot guide and PDF API.
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.screenshot({ path: 'page.webp', fullPage: true, type: 'jpeg', quality: 85 });
await browser.close();
5. Choose printing, PDF, or a screenshot
| Requirement | Best fit | Reason |
|---|---|---|
| A person selects a printer | window.print() |
Uses the browser’s print dialog and the user’s printer settings. |
| Controlled paper layout | Print CSS plus PDF | @page, page breaks, margins, and print-only content describe a document. |
| Unattended PDF generation | Puppeteer or Playwright | Runs in a worker without opening a visible browser window. |
| Pixel or visual regression capture | Screenshot automation | Captures the rendered appearance instead of paginating it. |
| Managed capture at scale | Hosted screenshot API | Removes browser installation, patching, and worker management from your application. |
Decide first whether the output must behave like paper or must preserve the page’s visual composition. A full-page screenshot can be extremely tall and does not have reliable paper page breaks; a PDF can paginate content that would be continuous in a screenshot.
6. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. Send one GET request to receive a PNG, JPEG, WebP, or PDF. The API accepts full-page capture, element selectors, dark mode, device presets or custom viewports, retina scale, PDF paper settings, custom CSS and JavaScript, click and wait actions, hidden selectors, blocked requests and resource types, custom headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, caching, signed links, asynchronous jobs, webhooks, bulk capture, usage data, and OpenAPI tooling. Each feature is available on every plan.
Use the ScreenshotNeo API documentation for the complete parameter list. A basic PDF request is:
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o page.pdf
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("page.pdf", "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(`HTTP ${res.status}`);
const body = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('page.pdf', body));
ScreenshotNeo accepts consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing. The response identifies the result with X-Page-Verdict and X-Billed 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 per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free. For repeated captures, choose a cache TTL, set a deliberate wait condition, and inspect the verdict headers before retrying. For public image tags, use signed links; for long jobs, use asynchronous capture with signed webhooks.
Create a free ScreenshotNeo account with 1,000 screenshots a month and no card.
7. Reliability, performance, and cost considerations
- Wait for meaning, not just time. Prefer a ready selector or application event. Use a delay only when the page has no reliable readiness signal.
- Bound every operation. Set navigation, selector, and API timeouts so a stuck third-party request cannot consume a worker forever.
- Make rendering deterministic. Set the viewport, device scale, timezone, locale, media type, fonts, and authentication state explicitly.
- Control resource loading. Blocking ads, trackers, unnecessary requests, or resource types can reduce work and remove visual noise, but verify that required CSS, fonts, and images still load.
- Reuse browsers carefully. A long-lived browser process avoids startup overhead, while isolated contexts prevent cookies and local storage from leaking between jobs.
- Retry selectively. Retry transient navigation failures with backoff. Do not repeatedly retry bot checks, invalid URLs, authentication failures, or a selector that never exists.
- Cache stable pages. A cache TTL avoids rendering the same unchanged URL repeatedly. Invalidate it when the underlying content changes.
- Measure the output. Record URL, viewport, media type, wait condition, status, output bytes, and the final verdict. Compare PDFs by page count and screenshots by dimensions before accepting a job.
With a self-hosted browser, your cost includes compute, memory, browser updates, fonts, sandbox configuration, concurrency limits, queueing, and operational time. A hosted API changes that into request pricing and provider limits. Check the provider’s current terms for limits that matter to your workload.
8. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Print dialog opens before data appears | Printing starts before asynchronous rendering completes. | Disable the button until data is ready, or wait for a ready selector in automation. |
| Navigation or cookie banner appears in the PDF | No print rule hides it, or the page changes after capture begins. | Add explicit print CSS; in automation wait for the post-load state and hide selectors before capture. |
| PDF has missing colors | Background printing is disabled or the browser optimizes colors. | Set printBackground: true and use -webkit-print-color-adjust: exact. |
| Fonts change between runs | Capture occurs before web fonts finish loading. | Await document.fonts.ready and ensure the font files are reachable. |
| Full-page screenshot is clipped | The page has a fixed-height scrolling container rather than document height. | Capture the scrolling element separately or expand it with custom CSS before capture. |
networkidle never occurs |
Analytics, WebSockets, or polling keep connections open. | Use domcontentloaded plus a selector or application-ready signal and a timeout. |
| Images are blank or lazy images are missing | Images load only after scrolling or intersection events. | Scroll through the page, wait for image completion, or use a capture service that loads lazy images. |
| Headless browser fails to launch | Missing browser binaries, sandbox permissions, or incompatible dependencies. | Install the matching browser, use the runtime’s documented sandbox configuration, and check container libraries. |
| API response is not an image or PDF | The request failed or returned an error body. | Check HTTP status and response headers before writing bytes; log the error body and request parameters. |
9. FAQ
Can JavaScript print a page silently?
window.print() opens the browser print dialog and is intended for an interactive user flow. Silent server-side output requires a headless browser or an API.
Does window.print() create a PDF file?
It lets the user choose a printer or a browser-provided “Save as PDF” destination. It does not give page JavaScript a portable PDF file directly.
Should I use a screenshot or PDF for invoices?
Use PDF when paper dimensions, page breaks, margins, and selectable text matter. Use a screenshot when visual appearance is the requirement.
Why does a screenshot differ from what I see locally?
Viewport size, device scale, fonts, timezone, authentication, animations, network timing, and media type can all change the rendered result. Set each deliberately and wait for the same application-ready state.
Can I print only one element?
In your own page, temporarily hide other elements with print CSS. In automation or ScreenshotNeo, capture the target element with a CSS selector.


