Puppeteer ports for Deno need a local Chrome, and Deno Deploy isolates do not ship one. The standard library already has everything you need: fetch, URLSearchParams, and Deno.env.
200 renders a month, no card.
On your laptop, Deno can drive Chrome through a Puppeteer port or Astral, provided a browser is installed and the script runs with --allow-run and --allow-net. On Deno Deploy that path closes: the isolate has no Chromium binary, no subprocess permission, and no writable disk to unpack one into. The usual advice is to connect to a remote browser over WebSocket, which brings back everything you were trying to avoid: session lifecycle, timeouts, and a second vendor billed by the minute.
A screenshot API keeps the whole integration inside the Deno standard runtime. The handler below is one fetch call and a Response. It runs identically with deno run locally and on Deno Deploy, and it needs no permissions beyond --allow-net and --allow-env.
References: Deno Deploy documentation.
Call GET https://app.snap-render.com/v1/screenshot with the target in the url parameter and your key in the X-API-Key header. The response body is the finished image (or PDF when format=pdf). Ads and cookie banners are blocked by default.
# Local
export SNAPRENDER_API_KEY=sk_live_your_key_here
deno run --allow-net --allow-env main.ts
# Deno Deploy: Project > Settings > Environment Variables > SNAPRENDER_API_KEY
const API_KEY = Deno.env.get('SNAPRENDER_API_KEY') ?? '';
Deno.serve(async (req: Request) => {
const target = new URL(req.url).searchParams.get('url');
if (!target) {
return Response.json({ error: 'url query parameter is required' }, { status: 400 });
}
const params = new URLSearchParams({
url: target,
format: 'png',
width: '1280',
height: '800',
full_page: 'true',
dark_mode: 'false',
cache: 'true',
cache_ttl: '3600',
});
const upstream = await fetch(`https://app.snap-render.com/v1/screenshot?${params}`, {
headers: { 'X-API-Key': API_KEY },
});
if (!upstream.ok) {
const body = await upstream.json().catch(() => null);
const code = body?.error?.code ?? 'UPSTREAM_ERROR';
// 429 covers RATE_LIMITED (retry after a minute) and QUOTA_EXCEEDED (monthly)
return Response.json({ error: code }, { status: upstream.status });
}
return new Response(upstream.body, {
headers: { 'Content-Type': 'image/png', 'Cache-Control': 'public, max-age=3600' },
});
});
On Deno Deploy there is no persistent disk, so stream the response straight to the client or forward the bytes to S3-compatible storage with the AWS SDK for JavaScript v3, which runs on Deno.
// deno run --allow-net --allow-env --allow-write capture.ts https://example.com
const [target] = Deno.args;
const params = new URLSearchParams({ url: target, format: 'pdf', full_page: 'true' });
const res = await fetch(`https://app.snap-render.com/v1/screenshot?${params}`, {
headers: { 'X-API-Key': Deno.env.get('SNAPRENDER_API_KEY') ?? '' },
});
if (!res.ok) {
console.error(await res.text());
Deno.exit(1);
}
await Deno.writeFile('page.pdf', new Uint8Array(await res.arrayBuffer()));
console.log('saved page.pdf, remaining credits:', res.headers.get('X-Remaining-Credits'));
Caching is off by default. Pass cache=true and a cache_ttl in seconds (default 86400, maximum depends on plan). A repeat request for the same URL and parameters inside that window is served from the cache and does not count against your monthly quota. The X-Cache response header reports HIT or MISS, and X-Remaining-Credits shows what is left this month.
Every error body is { "error": { "code", "message", "status" } }.
Full parameter list, POST with raw HTML, signed URLs, and batch jobs are in the API reference. The OpenAPI spec is at /openapi.json.
Not with a local browser. Deploy isolates have no Chromium binary and no subprocess permission. Those libraries only work on Deploy when pointed at a remote browser endpoint over WebSocket.
Locally, --allow-net for the API call and --allow-env to read the key. The disk example adds --allow-write. On Deno Deploy permissions are implicit.
Pass format=pdf. The response is a PDF document of the rendered page. It is a page render, not a print-optimised layout, so long pages become tall PDF pages when full_page=true.
No. With cache=true and a cache_ttl, a repeat request for the same URL and parameters inside the window returns the stored image and is not billed as a render.
Yes. The free plan includes 200 renders a month with every feature enabled, and no card is required.
Language quickstarts: Node.js, Python, cURL. Cost comparison: Puppeteer on Lambda vs an API.
Or skip the setup entirely
Everything in this guide is one GET request with SnapRender. No browser to babysit, no timeouts to tune.
200 screenshots a month free. First render in under a minute.
Get a free API key, 200 renders a month, no card.