Screenshot API for Cloudflare Workers

Generate screenshots and PDFs from the edge — single fetch call to SnapAPI, no browser runtime, no Puppeteer, works globally on Cloudflare's network.

Get Free API Key

Why SnapAPI Works Perfectly on Workers

Cloudflare Workers can't run native binaries or spawn processes — that rules out Puppeteer, Playwright, and any headless browser directly. SnapAPI solves this: your Worker makes one outbound HTTPS fetch to the SnapAPI endpoint and receives a screenshot or PDF back. The browser runs on SnapAPI's infrastructure; your Worker stays lean and fast.

Basic Screenshot Worker

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const target = url.searchParams.get('url');
    if (!target) return new Response('Missing ?url=', { status: 400 });

    const snap = new URL('https://api.snapapi.pics/v1/screenshot');
    snap.searchParams.set('access_key', env.SNAPAPI_KEY);
    snap.searchParams.set('url', target);
    snap.searchParams.set('format', 'png');
    snap.searchParams.set('width', '1280');
    snap.searchParams.set('full_page', 'false');

    const res = await fetch(snap.toString());
    if (!res.ok) return new Response('Screenshot failed', { status: 502 });

    return new Response(res.body, {
      headers: {
        'Content-Type': 'image/png',
        'Cache-Control': 'public, max-age=3600',
      },
    });
  },
};

Using Cloudflare KV for Caching

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const target = url.searchParams.get('url');
    const cacheKey = 'snap:' + btoa(target).replace(/=/g, '');

    // Check KV cache first
    const cached = await env.SCREENSHOTS.get(cacheKey, { type: 'arrayBuffer' });
    if (cached) {
      return new Response(cached, {
        headers: { 'Content-Type': 'image/png', 'X-Cache': 'HIT' }
      });
    }

    // Generate fresh screenshot
    const snap = new URL('https://api.snapapi.pics/v1/screenshot');
    snap.searchParams.set('access_key', env.SNAPAPI_KEY);
    snap.searchParams.set('url', target);
    snap.searchParams.set('format', 'png');
    snap.searchParams.set('width', '1280');

    const res = await fetch(snap.toString());
    const buf = await res.arrayBuffer();

    // Store in KV with 1-hour TTL
    await env.SCREENSHOTS.put(cacheKey, buf, { expirationTtl: 3600 });

    return new Response(buf, {
      headers: { 'Content-Type': 'image/png', 'X-Cache': 'MISS' }
    });
  },
};

wrangler.toml Configuration

name = "screenshot-worker"
main = "src/index.js"
compatibility_date = "2024-01-01"

[[kv_namespaces]]
binding = "SCREENSHOTS"
id = "your-kv-namespace-id"

[vars]
# Set SNAPAPI_KEY as a secret: wrangler secret put SNAPAPI_KEY

Generating OG Images at the Edge

One of the most powerful use cases is generating Open Graph images on-the-fly from Workers. When a social crawler hits your og:image URL, the Worker generates a fresh screenshot of your OG template page and returns it instantly — no pre-rendering, no build step needed:

// workers/og-image.js
export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const title = url.searchParams.get('title') || 'My Site';
    const slug = url.searchParams.get('slug') || '';

    // Your OG template URL with query params
    const templateUrl = encodeURIComponent(
      'https://yoursite.com/og-template?title=' + encodeURIComponent(title)
    );

    const snap = new URL('https://api.snapapi.pics/v1/screenshot');
    snap.searchParams.set('access_key', env.SNAPAPI_KEY);
    snap.searchParams.set('url', templateUrl);
    snap.searchParams.set('format', 'png');
    snap.searchParams.set('width', '1200');
    snap.searchParams.set('height', '630');

    const res = await fetch(snap.toString());
    return new Response(res.body, {
      headers: {
        'Content-Type': 'image/png',
        'Cache-Control': 'public, max-age=86400, s-maxage=86400',
      },
    });
  },
};

PDF Generation from Workers

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

    const snap = new URL('https://api.snapapi.pics/v1/screenshot');
    snap.searchParams.set('access_key', env.SNAPAPI_KEY);
    snap.searchParams.set('url', target);
    snap.searchParams.set('format', 'pdf');
    snap.searchParams.set('pdf_format', 'A4');
    snap.searchParams.set('full_page', 'true');

    const res = await fetch(snap.toString());
    return new Response(res.body, {
      headers: {
        'Content-Type': 'application/pdf',
        'Content-Disposition': 'attachment; filename=page.pdf',
      },
    });
  },
};

Supported SnapAPI Features on Workers

All SnapAPI endpoints work from Cloudflare Workers: screenshots (PNG, WebP, JPEG), PDF generation (A4, Letter, landscape), full-page capture, custom viewport sizes, delay parameter for JS-heavy pages, custom CSS injection, and the scrape and extract endpoints. The Workers fetch API handles all of these identically — just change the query parameters.

Pricing

Free tier: 200 captures/month. $19/month for 5K, $79/month for 50K. Sign up at snapapi.pics — no credit card required for the free tier.

Cloudflare Workers + SnapAPI: Advanced Patterns

Cloudflare Workers is the most widely-used edge compute platform in 2026, powering millions of applications from simple redirects to full-stack apps with D1, KV, and R2. Adding screenshot and PDF generation to any Worker-based app is straightforward with SnapAPI — and the combination is uniquely powerful because your Worker runs globally while SnapAPI handles the browser infrastructure centrally.

Storing Results in R2

Cloudflare R2 is the natural storage layer for Workers-generated assets. Here's a complete pattern for generating a screenshot and storing it in R2 for permanent hosting:

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const target = url.searchParams.get('url');
    const key = 'screenshots/' + encodeURIComponent(target) + '.png';

    // Check R2 first
    const existing = await env.BUCKET.get(key);
    if (existing) {
      return new Response(existing.body, {
        headers: { 'Content-Type': 'image/png', 'X-Cache': 'HIT', 'Cache-Control': 'public, max-age=86400' }
      });
    }

    // Generate via SnapAPI
    const params = new URLSearchParams({
      access_key: env.SNAPAPI_KEY,
      url: target,
      format: 'png',
      width: '1280',
    });
    const res = await fetch('https://api.snapapi.pics/v1/screenshot?' + params);
    const buf = await res.arrayBuffer();

    // Store in R2
    await env.BUCKET.put(key, buf, { httpMetadata: { contentType: 'image/png' } });

    return new Response(buf, {
      headers: { 'Content-Type': 'image/png', 'X-Cache': 'MISS', 'Cache-Control': 'public, max-age=86400' }
    });
  }
};

Durable Objects for Rate Limiting

If you're exposing screenshot functionality to end users, use a Durable Object to enforce per-user rate limits without an external database:

export class RateLimiter {
  constructor(state) { this.state = state; }

  async fetch(request) {
    const count = (await this.state.storage.get('count')) || 0;
    const reset = (await this.state.storage.get('reset')) || 0;
    const now = Date.now();

    if (now > reset) {
      await this.state.storage.put('count', 1);
      await this.state.storage.put('reset', now + 3600000); // 1 hour window
      return new Response(JSON.stringify({ allowed: true }));
    }

    if (count >= 50) {
      return new Response(JSON.stringify({ allowed: false }), { status: 429 });
    }

    await this.state.storage.put('count', count + 1);
    return new Response(JSON.stringify({ allowed: true }));
  }
}

Queues for Async Screenshot Jobs

For high-throughput use cases, use Cloudflare Queues to decouple screenshot requests from results:

// Producer Worker: receives requests, enqueues jobs
export default {
  async fetch(request, env) {
    const body = await request.json();
    await env.SCREENSHOT_QUEUE.send({ url: body.url, callback: body.callback });
    return Response.json({ status: 'queued' });
  }
};

// Consumer Worker: processes queue, calls SnapAPI, stores result
export default {
  async queue(batch, env) {
    for (const msg of batch.messages) {
      const { url, callback } = msg.body;
      const params = new URLSearchParams({ access_key: env.SNAPAPI_KEY, url, format: 'png', width: '1280' });
      const res = await fetch('https://api.snapapi.pics/v1/screenshot?' + params);
      const buf = await res.arrayBuffer();
      const key = 'screenshots/' + Date.now() + '.png';
      await env.BUCKET.put(key, buf, { httpMetadata: { contentType: 'image/png' } });
      // Notify callback URL
      await fetch(callback, { method: 'POST', body: JSON.stringify({ key, status: 'done' }) });
      msg.ack();
    }
  }
};

Pages Functions Integration

If you use Cloudflare Pages, add SnapAPI calls in your Pages Functions under functions/ directory. Pages Functions have the same runtime as Workers and support the same environment variable bindings:

// functions/api/screenshot.js
export async function onRequest(context) {
  const { request, env } = context;
  const url = new URL(request.url).searchParams.get('url');
  const params = new URLSearchParams({ access_key: env.SNAPAPI_KEY, url, format: 'png', width: '1200' });
  const res = await fetch('https://api.snapapi.pics/v1/screenshot?' + params);
  return new Response(res.body, { headers: { 'Content-Type': 'image/png' } });
}

Why Not Use Browser Rendering in Workers?

Cloudflare has a Browser Rendering API in beta, but it's limited to headless Chrome operations via Puppeteer and has strict memory and time constraints per invocation. SnapAPI's approach is complementary: it offloads the browser entirely so your Worker stays within its CPU and memory budget. For high-volume screenshot generation, SnapAPI's dedicated browser pool will consistently outperform the Workers Browser Rendering API in throughput and reliability.

Free Tier and Pricing

SnapAPI's free tier gives 200 captures per month with no credit card. The Starter plan ($19/month) covers 5,000 captures — enough for most Cloudflare Worker projects. If you're building a product on top of SnapAPI and reselling screenshot functionality, the Growth plan ($79/month) gives 50,000 captures. All plans include screenshots, PDFs, scraping, and extraction endpoints.

Get Started with SnapAPI on Cloudflare Workers

Sign up at snapapi.pics and grab your API key from the dashboard. Add it to your Worker as a secret using wrangler secret put SNAPAPI_KEY. Deploy the example Worker above with wrangler deploy and your screenshot endpoint is live globally in seconds. The free tier gives 200 captures per month with no credit card required. For production workloads, the Starter plan at 19 dollars per month covers 5,000 captures and the Growth plan at 79 dollars per month handles 50,000. All plans include screenshots in PNG, WebP, and JPEG, PDF generation, full-page capture, custom viewport sizes, delay parameters for JavaScript-heavy pages, custom CSS injection, and the scrape and extract endpoints. The Cloudflare Workers integration requires no special configuration beyond the API key — the standard REST interface works identically from any Worker runtime version.