A screenshot API with the awkward parts handled: cookie banners blocked, ads hidden, lazy images triggered and full-page capture that works.
Build me a screenshot API that replaces ScreenshotOne: a URL in, a clean image out, with the cookie banners and the lazy images dealt with. STACK - Node 20+ with Fastify - SQLite through better-sqlite3 for jobs and cache metadata - Playwright with Chromium, in a pool of long-lived browser contexts - sharp for post-processing - Caddy in front THE DATA MODEL - jobs: id, url, url_hash, options_hash, status, output_path, bytes, width, height, ms, error, created_at, expires_at - cache: key, output_path, created_at, hits, last_hit_at — keyed on the URL plus every option that affects the image - api_keys: id, name, hash, rate_per_minute, concurrent_limit, monthly_bytes, revoked_at - blocklists: id, kind, pattern, source, updated_at — the banner and advert rules - usage: id, key_id, bytes, ms, at THE HARD PARTS, WHICH ARE ALL ABOUT THE PAGE NOT COOPERATING - Cookie banners. Inject the standard consent-manager opt-out rules at document start, and additionally hide known banner selectors from a maintained list. Where a consent framework is present, set its stored preference before the page runs rather than clicking a button that may not exist - Adverts and trackers blocked by request interception against a filter list, which also makes every capture faster and more deterministic - Lazy images. Scroll the full height in steps, wait for network idle at each, then scroll back to the top before capturing. Also force-load anything with a loading attribute and wait for images to decode - Animations and video: disable animations with a stylesheet injection, and set the media to a fixed frame, or the same URL produces a different image every time - Fonts: wait for the document's font loading to settle, or text renders in a fallback and the screenshot looks wrong in a way nobody can put their finger on - Sticky headers on a full-page capture, which repeat down the image. Detect fixed-position elements and hide them after the first viewport CAPTURE OPTIONS - Viewport width and height, device scale factor, and named device presets - Full page, viewport only, or a specific element by selector - Format: PNG, JPEG, WebP, or PDF, with quality - Dark or light colour scheme, a chosen language and time zone, and a geolocation if the page needs one - A delay, or a wait for a selector, or a wait for network idle — with a hard timeout over all of them - Custom CSS and JavaScript injected before capture, which is the escape hatch for everything not covered above - Blocking a list of selectors, and a cookie or header set for authenticated pages THE API - GET with signed parameters, so an image tag can be the whole integration - POST for the complex cases, synchronous under a deadline and asynchronous with a webhook above it - Cache by URL plus options hash: the same request returns the same stored file rather than launching a browser. This alone removes most of the load, and the cache lifetime is a parameter - Idempotency keys, and errors that name the cause — a timeout, a navigation failure, a selector never appearing RUNNING BROWSERS SAFELY, WHICH IS THE OPERATIONAL WHOLE OF IT - A pool of contexts in a small number of browser processes, recycled after a fixed number of captures because Chromium leaks - A hard concurrency cap. Each context is a hundred-odd megabytes and unbounded parallelism takes the machine down - A wall-clock timeout on every capture, enforced by killing the context, not by hoping - The browser in a container with no access to the host, non-root, with a seccomp profile - This service fetches arbitrary URLs on request. Refuse private address ranges, link-local addresses and internal hostnames, resolve the name yourself and check the resolved address, and re-check after every redirect. Without that it is a server-side request forgery machine pointed at your own network - A memory limit and a restart policy, because a browser will eventually misbehave DELIVERY - Images stored by content hash with immutable cache headers - Optional post-processing with sharp: resize, crop, quality, format conversion, and a watermark if wanted - A retention policy with a sweeper, and a disk watchdog that refuses jobs rather than filling the volume OPERATIONS - .env: DATABASE_PATH, STORAGE_PATH, BASE_URL, SIGNING_SECRET, MAX_CONCURRENCY, CAPTURE_TIMEOUT_MS, BLOCKLIST_URL - Migrations on boot, each once - The blocklists updated on a schedule, with the version recorded on each capture so a change in output is explicable - Health endpoint that captures a local fixture page through the real path WHAT MATTERS MOST The request guard and the cache. Refuse internal addresses before anything else — this is the vulnerability class this product category is built on — then make the cache key exact, so the same request never launches a second browser. Give me the repository, the container definition, migrations, .env.example, the blocklist updater, and a README with deploy steps behind Caddy and the network guard explained.
What you lose
- Block lists that remove cookie banners and adverts from every screenshot, kept current
- Browser capacity for a burst of requests
- Device emulation presets already configured
If you would rather not build
- Puppeteer, for the same job
What it costs
read from their page 15 Aug 2026
| Plan | Billed monthly | Billed yearly | Last read |
|---|---|---|---|
| — | $17/mo | — | 15 Aug 2026 |
Their pricing page is where these came from. Seeing a different price? Tell us.
The escape hatch
open source · no votes, no paid placement
Playwright
$0Drives a real browser with good wait primitives for screenshots.
microsoft/playwrightfree · open source
browserless
$0A self-hostable container exposing a browser over an API.
browserless/browserlessfree · open source
Why this verdict
our own opinion · changed only by a person
88/100
Verdict yes at 88. Playwright does the rendering; the wait sequence and the private-address block are the two things that make it usable and safe.
History
tracked since 10 Aug 2026 · nothing is ever overwritten
Questions about ScreenshotOne
answered from the record above
Is ScreenshotOne free?
No — the plan we track is $17 a month. Basic at around $17/month billed monthly for a fixed number of screenshots.
Can you replace ScreenshotOne by building your own?
YES. Replaceable in one session with an AI coding agent. Replacement score 88 out of 100, build time one session. Read what you lose before you decide.
How much does ScreenshotOne cost?
$17 a month on Basic — $204 a year. Recorded 10 Aug 2026.
What do you lose by replacing ScreenshotOne?
Block lists that remove cookie banners and adverts from every screenshot, kept current; Browser capacity for a burst of requests; Device emulation presets already configured. If any of those carry weight for you, keep paying.
Is there an open-source alternative to ScreenshotOne?
Yes: Playwright, browserless. The prompt on this page is for when you want it your way instead.
Related entries
same category first, most replaced first
Every week, something stops being worth paying for.
New verdicts, prices that moved, entries added. One email a week. Unsubscribe in one click. Nothing is being sent yet — your address is kept here, and the first issue is the first thing it is used for.
free forever · no tracking pixel · stored here, never passed to anyone

