ScreenshotAPI

JavaScript / TypeScript

Official screenshotapi-to SDK for JavaScript and TypeScript — capture screenshots from Node.js, Next.js, Vercel, Bun, Deno, and Cloudflare Workers.

The official screenshotapi-to SDK is a fully typed client with zero runtime dependencies. It works anywhere native fetch is available — Node.js 18+, Next.js, Vercel Functions, Bun, Deno, and Cloudflare Workers.

Package: screenshotapi-to on npm · Source & examples: github.com/miketromba/screenshotapi-js · Prefer raw HTTP? See Without the SDK.

Installation

npm install screenshotapi-to
pnpm add screenshotapi-to
yarn add screenshotapi-to
bun add screenshotapi-to

Authentication

Create an API key in the dashboard and keep it in a server-side environment variable.

export SCREENSHOTAPI_KEY="sk_live_your_key_here"
import { ScreenshotAPI } from 'screenshotapi-to'

const client = new ScreenshotAPI({
  apiKey: process.env.SCREENSHOTAPI_KEY!
})

Never expose your API key in browser JavaScript. For client-side apps, call your own backend route and let that route use the SDK. See Browser usage.

Quick Start

Capture a URL and save it to disk

save() captures the screenshot and writes the file in one call. It returns the response metadata.

import { ScreenshotAPI } from 'screenshotapi-to'

const client = new ScreenshotAPI({ apiKey: process.env.SCREENSHOTAPI_KEY! })

const metadata = await client.save({
  url: 'https://example.com',
  path: './example.png',
  type: 'png'
})

console.log(`Screenshot ID: ${metadata.screenshotId}`)
console.log(`Credits remaining: ${metadata.creditsRemaining}`)
console.log(`Captured in ${metadata.durationMs}ms`)

Or work with the raw image bytes

screenshot() returns the image as an ArrayBuffer so you can stream it, upload it, or transform it. It runs in any runtime, including edge.

const result = await client.screenshot({
  url: 'https://example.com',
  type: 'webp',
  quality: 85
})

console.log(result.contentType)            // "image/webp"
console.log(result.image.byteLength)       // image size in bytes
console.log(result.metadata.creditsRemaining)

save() writes files with Node.js APIs and is only available in Node-compatible runtimes. In edge runtimes (Cloudflare Workers, Vercel Edge), use screenshot() and return or store the ArrayBuffer yourself.

Methods

new ScreenshotAPI(config)

OptionTypeDefaultDescription
apiKeystring— (required)Your API key
baseUrlstringhttps://screenshotapi.toAPI base URL (proxies, tests)
timeoutnumber60000Request timeout in milliseconds

client.screenshot(options)

Returns Promise<ScreenshotResult>:

interface ScreenshotResult {
  image: ArrayBuffer            // raw image (or PDF) bytes
  contentType: string           // "image/png", "image/webp", "application/pdf", …
  metadata: {
    creditsRemaining: number
    screenshotId: string        // include this when contacting support
    durationMs: number
  }
}

client.save(options)

Takes the same options as screenshot() plus path: string. Writes the file to path and returns Promise<ScreenshotMetadata> (the metadata shape above). Node-only.

Options

Every screenshot parameter is available as a camelCase option on screenshot() and save().

OptionTypeDefaultDescription
urlstringRequired unless html is setURL to capture
htmlstringHTML document to render (switches to POST)
widthnumber1440Viewport width in pixels (max 1920)
heightnumber900Viewport height in pixels (max 10000)
fullPagebooleanfalseCapture the full scrollable page
type'png' | 'jpeg' | 'webp' | 'pdf''png'Output format
qualitynumber100JPEG/WebP quality, 1–100
colorScheme'light' | 'dark'Page defaultForce prefers-color-scheme
waitUntil'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2''networkidle2'Page readiness signal
waitForSelectorstringCSS selector to wait for
delaynumber0Extra wait after load (ms, max 30000)
blockAdsbooleanfalseBlock common ad networks
removeCookieBannersbooleanfalseAuto-remove cookie consent dialogs
cssInjectstringCSS injected before capture
jsInjectstringJavaScript evaluated before capture
stealthModebooleanfalseAnti-bot-detection browser fingerprint
devicePixelRatio1 | 2 | 31Retina/HiDPI scale
timezonestringServer defaultIANA timezone, e.g. America/New_York
localestringServer defaultBCP 47 locale, e.g. en-US
cacheTtlnumber0Cache identical captures for N seconds
preloadFontsbooleanfalsePreload Google Fonts before capture
removeElementsstring[]CSS selectors to remove
removePopupsbooleanfalseRemove common modals/overlays
mockupDevice'browser' | 'iphone' | 'macbook'Wrap output in a device frame (PNG)
geoLocation{ latitude: number; longitude: number; accuracy?: number }Browser geolocation emulation
const result = await client.screenshot({
  url: 'https://example.com/dashboard',
  width: 1440,
  height: 1200,
  fullPage: true,
  type: 'webp',
  quality: 90,
  colorScheme: 'dark',
  waitUntil: 'networkidle0',
  waitForSelector: '[data-ready="true"]',
  delay: 500,
  blockAds: true,
  removeCookieBanners: true,
  devicePixelRatio: 2,
  timezone: 'America/New_York',
  locale: 'en-US',
  cacheTtl: 300,
  removeElements: ['.modal', '#promo'],
  geoLocation: { latitude: 40.7128, longitude: -74.006, accuracy: 25 }
})

Render HTML & generate PDFs

Pass html instead of url to render a raw HTML string — the SDK automatically switches to POST /api/v1/screenshot. Combine with type: 'pdf' to produce documents like invoices or reports.

const pdf = await client.screenshot({
  html: '<main><h1>Invoice #1024</h1><p>Thank you for your business.</p></main>',
  type: 'pdf',
  width: 1200
})

// pdf.image is an ArrayBuffer of the PDF bytes

Error Handling

The SDK throws typed errors for API, network, and timeout failures. Every error extends ScreenshotAPIError.

import {
  ScreenshotAPI,
  AuthenticationError,
  InvalidAPIKeyError,
  InsufficientCreditsError,
  ScreenshotFailedError,
  ScreenshotTimeoutError,
  ScreenshotNetworkError
} from 'screenshotapi-to'

const client = new ScreenshotAPI({
  apiKey: process.env.SCREENSHOTAPI_KEY!,
  timeout: 30_000
})

try {
  await client.screenshot({ url: 'https://example.com' })
} catch (error) {
  if (error instanceof AuthenticationError) {
    console.error('Missing or malformed API key (401)')
  } else if (error instanceof InvalidAPIKeyError) {
    console.error('API key is revoked or invalid (403)')
  } else if (error instanceof InsufficientCreditsError) {
    console.error(`Out of credits (402). Balance: ${error.balance}`)
  } else if (error instanceof ScreenshotFailedError) {
    console.error(`Capture failed server-side (500): ${error.message}`)
  } else if (error instanceof ScreenshotTimeoutError) {
    console.error(`Timed out after ${error.timeoutMs}ms`)
  } else if (error instanceof ScreenshotNetworkError) {
    console.error('Could not reach ScreenshotAPI')
  } else {
    throw error
  }
}
ErrorWhen
AuthenticationError401 — API key missing or malformed
InsufficientCreditsError402 — no credits remaining (exposes .balance)
InvalidAPIKeyError403 — API key revoked or invalid
ScreenshotFailedError500 — capture failed server-side
ScreenshotTimeoutErrorRequest exceeded timeout (exposes .timeoutMs)
ScreenshotNetworkErrorNetwork/connection failure
ScreenshotAPIErrorBase class for all of the above

Framework Recipes

Next.js (App Router)

Proxy screenshots through a route handler so your key stays on the server.

// app/api/screenshot/route.ts
import { ScreenshotAPI } from 'screenshotapi-to'

const client = new ScreenshotAPI({ apiKey: process.env.SCREENSHOTAPI_KEY! })

export async function GET(request: Request) {
  const url = new URL(request.url).searchParams.get('url')
  if (!url) {
    return Response.json({ error: 'url is required' }, { status: 400 })
  }

  try {
    const { image, contentType } = await client.screenshot({ url, type: 'webp' })
    return new Response(image, {
      headers: {
        'Content-Type': contentType,
        'Cache-Control': 'public, max-age=3600'
      }
    })
  } catch {
    return Response.json({ error: 'Screenshot failed' }, { status: 500 })
  }
}

Express

import express from 'express'
import { ScreenshotAPI } from 'screenshotapi-to'

const app = express()
const client = new ScreenshotAPI({ apiKey: process.env.SCREENSHOTAPI_KEY! })

app.get('/screenshot', async (req, res) => {
  const url = req.query.url as string
  if (!url) return res.status(400).json({ error: 'url is required' })

  try {
    const { image, contentType } = await client.screenshot({ url, type: 'webp', quality: 80 })
    res.set('Content-Type', contentType)
    res.set('Cache-Control', 'public, max-age=3600')
    res.send(Buffer.from(image))
  } catch {
    res.status(500).json({ error: 'Screenshot failed' })
  }
})

Cloudflare Workers

save() isn't available at the edge — use screenshot() and return the ArrayBuffer directly.

import { ScreenshotAPI } from 'screenshotapi-to'

export default {
  async fetch(request: Request, env: { SCREENSHOTAPI_KEY: string }): Promise<Response> {
    const client = new ScreenshotAPI({ apiKey: env.SCREENSHOTAPI_KEY })
    const url = new URL(request.url).searchParams.get('url') ?? 'https://example.com'

    const { image, contentType } = await client.screenshot({ url, type: 'webp' })
    return new Response(image, { headers: { 'Content-Type': contentType } })
  }
}

Batch captures

screenshot() is a normal promise, so use Promise.allSettled for resilient parallel captures.

const urls = ['https://example.com', 'https://github.com', 'https://news.ycombinator.com']

const results = await Promise.allSettled(urls.map(url => client.save({ url, path: `./out/${new URL(url).hostname}.png` })))

results.forEach((result, i) => {
  if (result.status === 'fulfilled') console.log(`✓ ${urls[i]}`)
  else console.error(`✗ ${urls[i]}: ${result.reason.message}`)
})

More runnable examples ship with the package: Node.js, Next.js, Vercel Function, and Cloudflare Worker.

Browser usage

Never call ScreenshotAPI directly from the browser — it would expose your API key. Always proxy through a backend route (see the Next.js and Express recipes above).

// Frontend — calls your own proxy, not ScreenshotAPI
const response = await fetch(`/api/screenshot?url=${encodeURIComponent(url)}`)
const blob = await response.blob()
const imageUrl = URL.createObjectURL(blob)

Without the SDK

The SDK is a thin wrapper over a single HTTP endpoint, so you can always call it with native fetch and no dependencies:

const params = new URLSearchParams({ url: 'https://example.com' })
const response = await fetch(`https://screenshotapi.to/api/v1/screenshot?${params}`, {
  headers: { 'x-api-key': process.env.SCREENSHOTAPI_KEY! }
})

if (!response.ok) throw new Error((await response.json()).error)
const image = Buffer.from(await response.arrayBuffer())

See the Screenshot API reference for the full endpoint contract.

Requirements

  • Node.js 18+ (or any runtime with native fetch)
  • Zero runtime dependencies

Next steps

On this page