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 KeyCloudflare 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.
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',
},
});
},
};
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' }
});
},
};
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
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',
},
});
},
};
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',
},
});
},
};
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.
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 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.
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' }
});
}
};
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 }));
}
}
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();
}
}
};
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' } });
}
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.
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.
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.