Recommended Free Tools
Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
ActiveX is not required. For a screenshot of any URL as a browser renders it, run JavaScript in Node.js with Playwright or Puppeteer. If code must run inside the page itself, use html2canvas for a DOM-based image, or use the Screen Capture API when a person should choose a screen, window, or tab. These approaches solve different problems: a web page cannot silently capture an arbitrary URL or the user’s desktop.
Choose the method that matches your screenshot
| What you need | Best fit | Main limitation |
|---|---|---|
| Capture an arbitrary URL exactly as a browser renders it | Playwright or Puppeteer in Node.js | Requires a browser automation process outside the page |
| Turn selected DOM content into an image from a page feature | html2canvas | Reconstructs the image from DOM data; cross-origin content and unsupported CSS can differ from real pixels |
| Let a user select a display, window or tab | Screen Capture API | Always uses a browser permission and source picker; it is not silent URL capture |
The phrase “take a website screenshot with JavaScript” is therefore ambiguous. Decide first whether your JavaScript runs in Node.js (where it can launch a browser and open a URL) or in the document itself (where browser security rules apply).
Capture a rendered URL with Playwright
Playwright is the most direct general-purpose solution when the output should be a browser-rendered screenshot. Its Page API provides navigation and page.screenshot(); the screenshots guide documents full-page and element captures.
Install and run
- Install a current Node.js release.
- Create a project and install Playwright:
mkdir js-shot cd js-shot npm init -y npm install -D playwright npx playwright install chromium - Save this as
capture.js:
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
try {
const page = await browser.newPage({
viewport: { width: 1280, height: 800 },
deviceScaleFactor: 1
});
await page.goto('https://example.com', {
waitUntil: 'domcontentloaded',
timeout: 45_000
});
// Replace this with a page-specific readiness check when needed.
await page.screenshot({
path: 'screenshot.png',
fullPage: true,
type: 'png'
});
} finally {
await browser.close();
}
})();
Run node capture.js. A viewport screenshot omits fullPage (or sets it to false). To capture one element, locate it and call await page.locator('.hero').screenshot({ path: 'hero.png' }). Playwright also supports JPEG and WebP output; use the format and quality options documented in its API.
#1 Best Overall
Make readiness deterministic
networkidle can be useful for a page that finishes all requests, but it is not suitable for every site: analytics, advertisements and live data may keep connections open. Prefer a condition that represents the page being ready:
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('#report', { state: 'visible', timeout: 30_000 });
await page.waitForTimeout(500); // only when a known animation needs to settle
await page.screenshot({ path: 'report.png', fullPage: true });
Lazy-loaded images may not exist until the page is scrolled. Before a full-page shot, scroll through the document and wait for images where the site requires it:
await page.evaluate(async () => {
await new Promise(resolve => {
let y = 0;
const step = 600;
const timer = setInterval(() => {
window.scrollBy(0, step);
y += step;
if (y >= document.body.scrollHeight) {
clearInterval(timer);
window.scrollTo(0, 0);
resolve();
}
}, 100);
});
});
await page.waitForTimeout(300);
await page.screenshot({ path: 'lazy-page.png', fullPage: true });
For repeatable captures, fix the viewport, device scale factor, locale, timezone and browser version. Fonts, operating-system rendering, animations, video frames and third-party embeds can still vary, so do not promise pixel identity across environments.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsPuppeteer: an equivalent JavaScript option
Chrome for Developers describes Puppeteer as a high-level API for automating Chrome and Firefox, including screenshots. Install it with npm install puppeteer; the package manages a compatible browser in its standard setup.
Rank #2
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800, deviceScaleFactor: 1 });
await page.goto('https://example.com', {
waitUntil: 'domcontentloaded',
timeout: 45_000
});
await page.waitForSelector('body');
await page.screenshot({ path: 'puppeteer.png', fullPage: true });
} finally {
await browser.close();
}
})();
The same operational rules apply: choose a page-specific readiness signal, handle authentication explicitly, and close the browser in a finally block.
Run the same workflow through other interfaces
cURL
Browser automation itself is JavaScript, but a command-line HTTP client can call a screenshot service when you do not want to install a browser. ScreenshotNeo provides a GET endpoint; replace the URL and key:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo documentation for output and option details.
Python
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)
Node.js fetch
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 fs = require('node:fs');
fs.writeFileSync('shot.webp', Buffer.from(await res.arrayBuffer()));
Use html2canvas inside a web page
html2canvas walks the DOM and paints a canvas representation. It does not take the browser’s actual pixel buffer, and it only implements CSS properties it understands. This is useful for a “download this card” button, not for a guaranteed faithful capture of an arbitrary site.
<script src="https://cdn.jsdelivr.net/npm/html2canvas@latest/dist/html2canvas.min.js"></script>
<button id="save">Save card</button>
<section id="capture">Content to export</section>
<script>
document.querySelector('#save').addEventListener('click', async () => {
const canvas = await html2canvas(document.querySelector('#capture'));
const link = document.createElement('a');
link.download = 'capture.png';
link.href = canvas.toDataURL('image/png');
link.click();
});
</script>
Origin and iframe limits
Images generally must be same-origin or served with suitable cross-origin headers (or a permitted proxy). Once a canvas is tainted by an unreadable cross-origin image, toDataURL() cannot read it. A script in one origin also cannot inspect a cross-origin iframe. No client-side option bypasses these browser security boundaries. Check the library’s supported CSS list before relying on filters, complex blending, pseudo-elements or unusual fonts.
When the Screen Capture API is the right answer
The Screen Capture API is designed for user-selected sharing. A user gesture should call getDisplayMedia(), after which the browser presents a picker:
async function recordSelection() {
const stream = await navigator.mediaDevices.getDisplayMedia({
video: true,
audio: false
});
const video = document.createElement('video');
video.srcObject = stream;
await video.play();
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext('2d').drawImage(video, 0, 0);
canvas.toBlob(blob => {
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'selection.png';
a.click();
stream.getTracks().forEach(track => track.stop());
}, 'image/png');
}
This cannot be converted into a silent “open this URL and capture it” function. The user chooses the source and can stop sharing at any time.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One request returns PNG, JPEG, WebP or PDF. Before capture it accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be turned off. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Its 63 options cover full-page capture with lazy images, CSS-selector elements, dark mode, 12 device presets or a custom viewport, retina scale, PDF paper and page settings, HTML/CSS to image, custom JavaScript and CSS, pre-capture clicks, hidden selectors, selector/delay/network-idle waits, ad and tracker blocking, custom headers/cookies/user agent/Authorization, timezone and geolocation, transparent backgrounds, resizing, chosen-TTL caching, signed image links, asynchronous jobs with signed webhooks, 100-URL bulk calls, a usage API and an OpenAPI specification. Common parameter names used by other screenshot APIs also work, which eases migration.
Rank #4
An MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients. Every feature is available on every plan: 1,000 shots per month free without a card; Starter $5 for 3,000; Growth $15 for 15,000; Pro $39 for 60,000; Scale $99 for 250,000; and Business $249 for 1,000,000. Yearly billing gives two months free.
Create a free ScreenshotNeo account to use the 1,000 monthly shots without a card.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Troubleshooting checklist
The screenshot is blank or only partly loaded
- Wait for a meaningful selector instead of assuming navigation completion means rendering is finished.
- Scroll to trigger lazy loading, then capture.
- Increase the navigation timeout only after checking for a genuinely slow dependency.
- Disable animations with page CSS or wait for a known transition to finish.
Images or fonts are missing
- Confirm the URL is reachable from the capture environment and does not require an unprovided login.
- For html2canvas, fix same-origin or CORS headers; it cannot read a tainted canvas.
- Use a consistent browser image and font environment for repeatable output.
Playwright or Puppeteer will not launch
- Run the framework’s browser installation command and check that the process has permission to execute it.
- In containers, provide the required sandbox configuration according to your deployment’s security policy rather than disabling protections blindly.
- Close every browser in error paths so orphaned processes do not exhaust memory.
The page blocks automation or requires a challenge
Automation does not defeat authentication, access controls or a site’s restrictions. Capture only pages you are authorized to access. Use a permitted authenticated context, or stop and handle the challenge manually. A service may classify a bot check or failed load as unsuccessful rather than returning a usable image.
Full-page output is too large
Capture a specific element or viewport, reduce the device scale factor, choose JPEG/WebP where appropriate, or split a long document into deliberate sections. For PDFs, set paper size, margins, orientation and page ranges instead of producing one enormous image.
Best Value
Performance, reliability and cost decisions
- Reuse browser processes: launching Chromium for every URL is expensive; keep one browser and create isolated pages or contexts for a batch.
- Limit concurrency: too many pages consume CPU, RAM and network bandwidth and can trigger target-site rate limits.
- Cache intentionally: cache only when a previous render is acceptable; dynamic pages need a freshness policy.
- Record diagnostics: save the URL, viewport, browser version, readiness condition and error text with each job.
- Protect credentials: keep cookies, Authorization headers and API keys out of source control and logs.
- Prefer asynchronous jobs for volume: a queue with retries and a bounded timeout is safer than one long-running request per user action.
Which approach should you use?
- Choose Playwright or Puppeteer when a server-side process must open arbitrary URLs and save browser-rendered pixels.
- Choose html2canvas when your own page needs a convenient export of selected, same-origin DOM content and visual approximation is acceptable.
- Choose Screen Capture API when the user must explicitly select a screen, window or tab.
- Choose ScreenshotNeo when you want a hosted capture endpoint, cleanup of consent banners and popups, explicit billing verdicts, PDF and advanced controls, or MCP access for AI agents.
Frequently Asked Questions
Can JavaScript in a normal web page capture another website without permission?
No. Browser origin rules prevent arbitrary cross-origin inspection, and the Screen Capture API requires the user to choose a source.
Does Playwright require ActiveX or Internet Explorer?
No. It launches supported Chromium, Firefox or WebKit browsers through a Node.js package.
Is html2canvas pixel-perfect?
No. It reconstructs an image from DOM information and supports only the CSS it implements.
What should I do with a page that never reaches network idle?
Use a page-specific selector, event or bounded delay that indicates the content you need is ready.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

