Tutorial March 17, 2026 11 min read

How to Take Screenshots with JavaScript Using an API (2026 Guide)

Taking website screenshots from JavaScript used to mean shipping a 200MB Chromium binary, wrestling with Docker memory limits, and writing hundreds of lines of browser management code. In 2026, a screenshot API reduces all of that to a single HTTP request.

This guide covers every common JavaScript scenario: vanilla Node.js, ES modules, Next.js API routes, Cloudflare Workers, and browser-side captures. All examples use the SnapAPI JavaScript SDK and work with the free tier (200 requests/month, no credit card).

Quick Overview: What You Can Do

Operation Input Output Typical Use Case
Screenshot URL PNG / WebP / AVIF Link previews, social cards, monitoring
Full-page capture URL PNG Archiving, visual testing, audit trails
PDF generation URL or HTML PDF Reports, invoices, contracts
OG image URL 1200x630 PNG Social sharing previews

Installation

# npm
npm install snapapi-js

# yarn
yarn add snapapi-js

# pnpm
pnpm add snapapi-js

Get your free API key at snapapi.pics/register. Store it in an environment variable — never hard-code API keys in source files.

# .env
SNAPAPI_KEY=snap_live_xxxxxxxxxxxxxxxx

Basic Screenshot in Node.js

The simplest possible example: capture a URL and save it to disk.

import { SnapAPI } from 'snapapi-js';
import fs from 'fs';

const client = new SnapAPI(process.env.SNAPAPI_KEY);

// Capture a screenshot
const image = await client.screenshot({
  url: 'https://github.com/trending',
  format: 'png',
  viewport: { width: 1440, height: 900 }
});

fs.writeFileSync('./screenshot.png', image);
console.log('Saved screenshot.png');

That is the entire script. No browser binary to install, no Chromium to manage. The screenshot() call is an HTTPS request that returns a Buffer containing the raw image bytes.

Common Screenshot Options

Full-Page Capture

const fullPage = await client.screenshot({
  url: 'https://stripe.com/docs',
  format: 'png',
  full_page: true        // Scroll and capture the entire page height
});

Mobile and Device Emulation

// iPhone 15 Pro viewport
const mobile = await client.screenshot({
  url: 'https://example.com',
  format: 'png',
  device: 'iPhone 15 Pro'   // Sets viewport + user agent automatically
});

// Android tablet
const tablet = await client.screenshot({
  url: 'https://example.com',
  format: 'png',
  viewport: { width: 768, height: 1024 }
});

Dark Mode

const dark = await client.screenshot({
  url: 'https://linear.app',
  format: 'webp',
  dark_mode: true
});

Clean Screenshots (No Ads, No Cookie Banners)

const clean = await client.screenshot({
  url: 'https://techcrunch.com',
  format: 'webp',
  block_ads: true,
  block_cookie_banners: true
});

Output Formats and File Size

// PNG — lossless, largest file
const png = await client.screenshot({ url, format: 'png' });

// WebP — 25-35% smaller than PNG, excellent quality
const webp = await client.screenshot({ url, format: 'webp', quality: 85 });

// AVIF — 40-60% smaller than PNG, near-lossless at quality 80
const avif = await client.screenshot({ url, format: 'avif', quality: 80 });

// JPEG — smallest for photos, lossy
const jpeg = await client.screenshot({ url, format: 'jpeg', quality: 75 });

Using the REST API Directly (No SDK)

If you prefer not to install the SDK, the REST API is a straightforward GET or POST request.

// Using native fetch (Node 18+)
const response = await fetch(
  'https://api.snapapi.pics/v1/screenshot?' + new URLSearchParams({
    url: 'https://github.com',
    format: 'png',
    width: '1440',
    height: '900'
  }),
  {
    headers: { Authorization: `Bearer ${process.env.SNAPAPI_KEY}` }
  }
);

if (!response.ok) {
  const err = await response.json();
  throw new Error(`SnapAPI error: ${err.message}`);
}

const buffer = Buffer.from(await response.arrayBuffer());
fs.writeFileSync('./github.png', buffer);
cURL equivalent: You can test any screenshot request from your terminal before writing JavaScript code:

curl -H "Authorization: Bearer YOUR_KEY" "https://api.snapapi.pics/v1/screenshot?url=https://github.com&format=png" -o screenshot.png

Next.js API Route

One of the most common use cases is generating screenshots on demand from a Next.js application. Here is a complete API route that accepts a URL parameter and returns the screenshot image.

// app/api/screenshot/route.js  (Next.js App Router)
import { SnapAPI } from 'snapapi-js';
import { NextResponse } from 'next/server';

const client = new SnapAPI(process.env.SNAPAPI_KEY);

export async function GET(request) {
  const { searchParams } = new URL(request.url);
  const url = searchParams.get('url');
  const format = searchParams.get('format') || 'webp';

  if (!url) {
    return NextResponse.json({ error: 'url parameter is required' }, { status: 400 });
  }

  // Basic URL validation
  try {
    new URL(url);
  } catch {
    return NextResponse.json({ error: 'Invalid URL' }, { status: 400 });
  }

  try {
    const image = await client.screenshot({
      url,
      format,
      viewport: { width: 1440, height: 900 },
      block_ads: true,
      block_cookie_banners: true
    });

    const contentTypes = {
      png: 'image/png',
      webp: 'image/webp',
      avif: 'image/avif',
      jpeg: 'image/jpeg'
    };

    return new NextResponse(image, {
      headers: {
        'Content-Type': contentTypes[format] || 'image/png',
        'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400'
      }
    });
  } catch (error) {
    console.error('Screenshot failed:', error);
    return NextResponse.json({ error: 'Screenshot generation failed' }, { status: 500 });
  }
}

Call it from the client side:

// In a React component
function LinkPreview({ href }) {
  const screenshotUrl = `/api/screenshot?url=${encodeURIComponent(href)}&format=webp`;
  return (
    <img
      src={screenshotUrl}
      alt={`Preview of ${href}`}
      loading="lazy"
      width={640}
      height={360}
    />
  );
}

Cloudflare Workers

Cloudflare Workers cannot run Puppeteer or Playwright — no Node.js runtime, no file system, no native binaries. A screenshot API is the only practical option for capturing screenshots at the edge.

// Cloudflare Worker
export default {
  async fetch(request, env) {
    const { searchParams } = new URL(request.url);
    const targetUrl = searchParams.get('url');

    if (!targetUrl) {
      return new Response('url parameter required', { status: 400 });
    }

    const apiUrl = 'https://api.snapapi.pics/v1/screenshot?' + new URLSearchParams({
      url: targetUrl,
      format: 'webp',
      width: '1440',
      height: '900',
      block_ads: 'true'
    });

    const response = await fetch(apiUrl, {
      headers: { Authorization: `Bearer ${env.SNAPAPI_KEY}` }
    });

    if (!response.ok) {
      return new Response('Screenshot failed', { status: 502 });
    }

    // Stream the image back to the client with caching headers
    return new Response(response.body, {
      headers: {
        'Content-Type': 'image/webp',
        'Cache-Control': 'public, max-age=86400',
        'CF-Cache-Status': 'DYNAMIC'
      }
    });
  }
};

Batch Screenshot Capture

For capturing multiple URLs — a link aggregator, a directory site, a monitoring dashboard — process them in parallel with controlled concurrency.

import { SnapAPI } from 'snapapi-js';
import fs from 'fs/promises';
import path from 'path';

const client = new SnapAPI(process.env.SNAPAPI_KEY);

async function captureAll(urls, { concurrency = 5, outputDir = './screenshots' } = {}) {
  await fs.mkdir(outputDir, { recursive: true });

  // Process in batches to respect rate limits
  const results = [];
  for (let i = 0; i < urls.length; i += concurrency) {
    const batch = urls.slice(i, i + concurrency);
    const batchResults = await Promise.allSettled(
      batch.map(async (url) => {
        const slug = new URL(url).hostname.replace(/\./g, '-');
        const filepath = path.join(outputDir, `${slug}.webp`);

        const image = await client.screenshot({
          url,
          format: 'webp',
          quality: 85,
          block_ads: true,
          block_cookie_banners: true
        });

        await fs.writeFile(filepath, image);
        return { url, filepath, size: image.length };
      })
    );
    results.push(...batchResults);
    console.log(`Captured batch ${Math.floor(i / concurrency) + 1}`);
  }

  return results;
}

// Usage
const urls = [
  'https://vercel.com',
  'https://railway.app',
  'https://render.com',
  'https://fly.io',
  'https://supabase.com'
];

const results = await captureAll(urls, { concurrency: 3 });
results.forEach(r => {
  if (r.status === 'fulfilled') {
    console.log(`OK  ${r.value.url} (${(r.value.size / 1024).toFixed(1)} KB)`);
  } else {
    console.log(`ERR ${r.reason}`);
  }
});

Error Handling and Retries

For production use, add retry logic for transient failures and proper error categorization.

import { SnapAPI } from 'snapapi-js';

const client = new SnapAPI(process.env.SNAPAPI_KEY);

async function screenshotWithRetry(options, { retries = 3, delay = 1000 } = {}) {
  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      return await client.screenshot(options);
    } catch (error) {
      const isRetryable = error.status === 429 || error.status >= 500;

      if (!isRetryable || attempt === retries) {
        throw error;
      }

      // Exponential backoff
      const wait = delay * Math.pow(2, attempt - 1);
      console.warn(`Attempt ${attempt} failed (${error.status}), retrying in ${wait}ms`);
      await new Promise(resolve => setTimeout(resolve, wait));
    }
  }
}

// Usage
const image = await screenshotWithRetry({
  url: 'https://example.com',
  format: 'webp'
});

TypeScript Usage

The snapapi-js package ships with full TypeScript definitions.

import { SnapAPI, ScreenshotOptions } from 'snapapi-js';

const client = new SnapAPI(process.env.SNAPAPI_KEY!);

const options: ScreenshotOptions = {
  url: 'https://linear.app',
  format: 'webp',
  viewport: { width: 1440, height: 900 },
  full_page: false,
  dark_mode: true,
  block_ads: true
};

const image: Buffer = await client.screenshot(options);

Use Cases by JavaScript Framework

Try SnapAPI Free

200 requests/month included. No credit card required. JavaScript SDK, TypeScript types, and full REST API access.

Get Free API Key →

Frequently Asked Questions

Can I use this in the browser (client-side JavaScript)?

You should call the SnapAPI from your server, not directly from client-side JavaScript, to avoid exposing your API key. Build a thin proxy route (like the Next.js example above) that your frontend calls.

How long does a screenshot take?

Typical response time is 1.5-3 seconds for most pages. Pages with heavy JavaScript or slow server responses take longer. The delay parameter adds additional wait time after page load for lazy-loaded content.

Does it work with pages that require login?

Yes — pass cookies or headers in the options object to authenticate with the target page.

What is the maximum screenshot size?

Full-page screenshots are limited to 16,000 pixels in height. For very long pages, consider capturing sections or generating a PDF instead.