Driver FixRecommendedSound, Wi-Fi or graphics acting up? Check drivers firstFind missing or outdated drivers fast.Check DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan Now×
Skip to content
World desk7 min

How to Build a Puppeteer Screenshot API with Node.js

A runnable Node.js and Puppeteer screenshot API, with validated capture options, HTTP image responses, deployment trade-offs, and common fixes.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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:

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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl --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.

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: fullPage captures beyond the viewport. Long pages can create much larger images and take longer to capture.
  • Clipped region: clip captures 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: type chooses the image type. PNG is Puppeteer’s documented default; quality applies to lossy formats and does not apply to PNG. Keep the accepted formats and quality range explicit.
  • Transparent background: omitBackground omits the default page background where a transparent output is appropriate.
  • File output: path can 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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

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 for format, quality, and fullPage.
  • 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 quality parameter 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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.