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 | 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 -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
- Express.js: Screenshot microservice that other services call internally. Cache results in Redis with a URL-hash key.
- Next.js: Dynamic OG image generation in API routes, link preview cards in your feed UI.
- Remix: Loader functions that return screenshots for admin dashboards or reporting pages.
- Cloudflare Workers: Edge screenshot service with automatic CDN caching via Cache API.
- AWS Lambda: Serverless screenshot processing triggered by S3 uploads or SQS messages.
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.