Recommended Free Tools
You can build a small screenshot API with Node.js by accepting a URL, opening it in Puppeteer, capturing the page with page.screenshot(), and returning the image bytes in an HTTP response. The example below uses Node’s built-in HTTP module, limits the capture options it accepts, and returns PNG, JPEG, or a JSON error. It is a starting point for a controlled service, not a safe public endpoint for arbitrary URLs.
What the API does
The request flow is deliberately small: a client sends a target URL and optional capture settings; the server validates the inputs, launches a browser, navigates to the page, captures an image, and writes those bytes to the response. Puppeteer’s documented workflow follows the same browser–page–navigation–screenshot sequence. By default, Page.screenshot() returns a Uint8Array; the example converts it to a Node.js Buffer for the HTTP response.
As an Amazon Associate I earn from qualifying purchases.
This version launches a browser for each request so its lifecycle is easy to see and each browser is closed after the capture. That is simple for a low-volume or local demonstration, but it adds startup work to every request. The deployment section explains the trade-offs before you expose it to real traffic.
Build and run the Node.js service
1. Install Puppeteer
Create a project and install Puppeteer. Its installation provides the package and browser setup needed by the example; installation details and runtime compatibility can depend on your environment.
#1 Best Overall
mkdir puppeteer-shot-api
cd puppeteer-shot-api
npm init -y
npm install puppeteer
2. Save the server
Save this as server.js. It provides GET /shot?url=.... The optional fullPage, format, and quality parameters are parsed explicitly rather than passed through wholesale to Puppeteer.
const http = require('node:http');
const { URL } = require('node:url');
const puppeteer = require('puppeteer');
const port = Number(process.env.PORT) || 3000;
function sendJson(res, statusCode, body) {
res.writeHead(statusCode, { 'content-type': 'application/json; charset=utf-8' });
res.end(JSON.stringify(body));
}
const server = http.createServer(async (req, res) => {
let requestUrl;
try {
requestUrl = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
} catch {
sendJson(res, 400, { error: 'Invalid request URL' });
return;
}
if (req.method !== 'GET' || requestUrl.pathname !== '/shot') {
sendJson(res, 404, { error: 'Use GET /shot?url=...' });
return;
}
const target = requestUrl.searchParams.get('url');
let targetUrl;
try {
if (!target) throw new Error('Missing url');
targetUrl = new URL(target);
if (targetUrl.protocol !== 'http:' && targetUrl.protocol !== 'https:') {
throw new Error('Unsupported protocol');
}
} catch {
sendJson(res, 400, { error: 'Provide a valid http or https url parameter' });
return;
}
const format = requestUrl.searchParams.get('format') || 'png';
if (format !== 'png' && format !== 'jpeg') {
sendJson(res, 400, { error: 'format must be png or jpeg' });
return;
}
const fullPageValue = requestUrl.searchParams.get('fullPage') || 'false';
if (fullPageValue !== 'true' && fullPageValue !== 'false') {
sendJson(res, 400, { error: 'fullPage must be true or false' });
return;
}
const fullPage = fullPageValue === 'true';
let quality;
if (requestUrl.searchParams.has('quality')) {
quality = Number(requestUrl.searchParams.get('quality'));
if (!Number.isInteger(quality) || quality < 1 || quality > 100) {
sendJson(res, 400, { error: 'quality must be an integer from 1 to 100' });
return;
}
if (format === 'png') {
sendJson(res, 400, { error: 'quality applies to jpeg, not png' });
return;
}
}
let browser;
try {
browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
await page.goto(targetUrl.href, {
waitUntil: 'domcontentloaded',
timeout: 30000
});
const screenshotOptions = { type: format, fullPage };
if (quality !== undefined) screenshotOptions.quality = quality;
const image = await page.screenshot(screenshotOptions);
res.writeHead(200, {
'content-type': format === 'jpeg' ? 'image/jpeg' : 'image/png',
'cache-control': 'no-store'
});
res.end(Buffer.from(image));
} catch (error) {
if (!res.headersSent) {
sendJson(res, 502, { error: 'Page navigation or screenshot failed' });
} else {
res.destroy(error);
}
} finally {
if (browser) await browser.close().catch(() => {});
}
});
server.listen(port, () => {
console.log(`Screenshot API listening on http://localhost:${port}`);
});
3. Start it and make a request
node server.js
In another terminal, URL-encode the page address so characters such as & do not get treated as separate API parameters:
Rank #2
curl --get 'http://localhost:3000/shot'
--data-urlencode 'url=https://example.com'
--output page.png
The response body is the image itself, not JSON or base64. To request a full-page JPEG with a chosen quality:
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Clear out junk files and repair common Windows errors3Fix the driver behind crashes, sound loss and screen glitchescurl --get 'http://localhost:3000/shot'
--data-urlencode 'url=https://example.com'
--data-urlencode 'format=jpeg'
--data-urlencode 'quality=80'
--data-urlencode 'fullPage=true'
--output page.jpg
Request and response behavior
Accepted inputs
| Parameter | Behavior in this example |
|---|---|
url |
Required absolute URL using HTTP or HTTPS. Invalid values receive HTTP 400. |
format |
Optional; png by default or jpeg. Other values receive HTTP 400. |
quality |
Optional integer from 1 to 100 for JPEG. PNG does not use quality, so a PNG request that includes it receives HTTP 400. |
fullPage |
Optional; accepts only true or false. Defaults to false, which captures the viewport rather than requesting a full-page image. |
The service sets the viewport to 1280 by 800 pixels for every capture. That determines the page’s viewport layout; fullPage=true asks Puppeteer to capture the full page rather than only that viewport. A successful response has an image content type and image bytes. Invalid request parameters return HTTP 400; navigation or capture failures return HTTP 502 with a short JSON error. The example intentionally does not send browser error details to callers.
Rank #3
Choose capture options deliberately
Puppeteer’s screenshot options cover several distinct needs. Extend the endpoint by adding a small, validated input for an option you actually want to support; do not copy arbitrary query parameters into the screenshot options object.
- Full page:
fullPagecaptures beyond the viewport. Long pages can create much larger images and take longer to capture. - Clipped region:
clipcaptures a specified rectangle rather than the whole viewport or page. Define and validate its dimensions in your API contract before accepting caller-provided coordinates. - Output type and quality:
typechooses the image type. PNG is Puppeteer’s documented default;qualityapplies to lossy formats and does not apply to PNG. Keep the accepted formats and quality range explicit. - Transparent background:
omitBackgroundomits the default page background where a transparent output is appropriate. - File output:
pathcan save the screenshot to a path instead of relying only on returned bytes. Decide whether the API should return bytes, save to local storage, or hand off to object storage; that storage policy is an application choice. - One element: Puppeteer also provides
ElementHandle.screenshot()for capturing a particular element. An endpoint for this needs a defined way to identify and wait for that element.
Deployment, reliability, and cost trade-offs
Browser lifecycle and throughput
This example pays the cost of starting and closing Chrome on every request. That keeps cleanup straightforward but increases per-request latency and browser startup work. Reusing a browser process can avoid repeated launches, but then the service must manage page cleanup, concurrent work, browser failures, and shutdown. Puppeteer documents that some operations, including creating or closing a page in the same browser context, wait for an active screenshot to finish; bringToFront() does not wait. Treat browser and page lifecycle as part of the service design rather than assuming every operation completes independently.
Rank #4
Large full-page captures consume more memory and produce more bytes to transfer than viewport captures. Requests that navigate slowly can also hold browser resources until completion or timeout. The example sets a 30-second navigation timeout; that is a code choice for this demonstration, not a guarantee about how long all sites take to render or a universal service limit.
Free tools Windows power users keep installed
One-click scans. No signup required.
Do not expose arbitrary navigation without a security design
This sample validates URL syntax and restricts the scheme to HTTP or HTTPS, but that is not a production security boundary. A server that navigates to caller-supplied addresses can be misused to make requests from the server’s network. Before exposing a service beyond a trusted environment, design and independently verify controls for permitted destinations, access, resource consumption, and network reachability. The implementation here does not establish those controls; use it locally or behind a boundary where the caller and destinations are controlled.
Containers
Puppeteer’s official Docker image includes Chrome for Testing and its required dependencies. Puppeteer’s documented sandbox-mode example uses the SYS_ADMIN capability, and its guide recommends an init process such as --init or a custom entrypoint to manage child processes. These are details of that documented container setup, not a universal deployment prescription for every container platform. Confirm the chosen environment’s browser and sandbox requirements before deployment.
Troubleshooting
- Browser launch fails: Check that the environment has the browser and dependencies required by the installed Puppeteer setup, and review the container’s sandbox configuration. The official Puppeteer Docker image is one documented way to provide Chrome for Testing and dependencies.
- The API returns HTTP 400: Check that the request uses
GET /shot, includes a URL-encoded absolute HTTP or HTTPS URL, and uses only the documented values forformat,quality, andfullPage. - The API returns HTTP 502: The example maps navigation or screenshot exceptions to this response. Verify the target is reachable from the service and that navigation finishes within the configured timeout. The response deliberately omits internal browser error details.
- The saved file is not an image: Check the HTTP status before treating the response body as an image. Errors are JSON; successful captures are PNG or JPEG according to the request.
- PNG quality is rejected: Remove the
qualityparameter or request JPEG. Puppeteer’s documentation specifies that the quality setting does not apply to PNG. - Captures are slow or memory-heavy: Check whether the request asks for a full-page capture, and account for browser startup on every request in this version. Avoid accepting unlimited concurrent work without a capacity and queueing design.
Or skip the browser setup
If you need an HTTP screenshot endpoint without installing or operating Puppeteer, ScreenshotNeo accepts a URL in one request and returns a screenshot. Its API and options are documented at ScreenshotNeo and the ScreenshotNeo API documentation.
Quick Recap
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Cookie and consent banners are accepted and removed before capture, along with known newsletter popups and chat widgets; each of those steps can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and responses identify the page verdict and billing status in headers. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents and MCP clients. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Sign up for ScreenshotNeo’s free plan.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallCrashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteProduct 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.




