A screenshot API: give it a URL and it returns a rendered image or PDF, handling fonts, cookie banners, lazy loading and full-page capture.
Build me a screenshot and PDF API that replaces Urlbox: a URL in, a rendered image out, with the awkward parts handled. STACK - Node 20+ with Fastify - SQLite through better-sqlite3 for jobs and cache metadata - Playwright with Chromium, in a pool of long-lived contexts - sharp for post-processing - Caddy in front THE DATA MODEL - jobs: id, url, url_hash, options_hash, kind, 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 changes the output - api_keys: id, name, hash, rate_per_minute, concurrent_limit, monthly_bytes, revoked_at - blocklists: id, kind, pattern, source, updated_at - usage: id, key_id, bytes, ms, at THE AWKWARD PARTS, WHICH ARE THE PRODUCT - Cookie banners. Set the consent framework's stored preference before the page runs, and hide known banner selectors from a maintained list. Clicking a button that may not exist is not a strategy - Adverts and trackers blocked by request interception against a filter list, which also makes every capture faster and far more deterministic - Late web fonts. Wait for the document's font loading to settle before capturing, or text renders in a fallback and the image looks subtly wrong in a way nobody can name - Lazy images. Scroll the full height in steps, wait for the network to settle at each, force-load anything deferred, wait for images to decode, then return to the top - Animations and video, which make the same URL produce a different image every time. Inject a stylesheet that stops animations and set media to a fixed frame - Sticky headers on a full-page capture, which repeat down the image. Detect fixed-position elements and hide them below the first viewport - Getting these six right is the entire difference between this and a five-line Playwright script OPTIONS - Viewport size, device scale factor, and named device presets - Full page, viewport, or a specific element by selector - PNG, JPEG, WebP, or PDF with paper size, margins, orientation and background - Colour scheme, language, time zone, geolocation - A delay, a wait for a selector, or a wait for network idle, all under one hard timeout - Injected CSS and JavaScript, which is the escape hatch for anything not covered - Selectors to hide, cookies and headers to set for an authenticated page THE API - GET with signed parameters, so an image tag is the whole integration - POST for the complex cases: synchronous under a deadline, asynchronous with a webhook above it - Cache on the URL plus the options hash, so the same request returns the stored file rather than launching a browser. That alone removes most of the load - Idempotency keys, and errors that name the cause — a navigation failure, a timeout, a selector that never appeared RUNNING BROWSERS WITHOUT LOSING THE MACHINE - A pool of contexts across a small number of browser processes, recycled after a fixed number of captures because Chromium leaks - A hard concurrency cap; each context costs about a hundred megabytes and unbounded parallelism takes the box down - A wall-clock timeout enforced by killing the context, not by hoping - The browser in a container: non-root, read-only root filesystem, no host access, a seccomp profile - Memory limit and a restart policy, because a browser will eventually misbehave THE SECURITY PROPERTY THAT MATTERS - This service fetches URLs that a caller chooses. Refuse private address ranges, loopback, link-local and internal hostnames — resolve the name yourself, check the resolved address, and re-check after every redirect - Without that, it is a server-side request forgery machine sitting inside your network, and this is the vulnerability class the whole category is built on. It goes in before any capture option DELIVERY - Output stored by content hash with immutable cache headers - Post-processing with sharp: resize, crop, quality, format, 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 - Blocklists updated on a schedule with the version recorded per capture, so a change in output is explicable - Health endpoint that captures a local fixture through the real path WHAT MATTERS MOST The request guard and determinism. Refuse internal addresses first, then make the same URL produce the same image twice by settling fonts, animations and lazy loading. A screenshot service that returns a different picture every call is a screenshot service nobody can build on. 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
- A fleet of browsers somebody else keeps patched and warm
- Handling for cookie banners, lazy-loaded images and web fonts that arrive late
- Capacity for a burst of requests without you provisioning anything
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 |
|---|---|---|---|
| — | $19/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 for screenshots and PDFs, with good wait primitives.
microsoft/playwrightfree · open source
browserless
$0A container that exposes a browser over an API; self-hostable.
browserless/browserlessfree · open source
Why this verdict
our own opinion · changed only by a person
87/100
Verdict yes at 87. Playwright does the rendering; the font and lazy-loading waits are what make the output usable, and the allow list is what keeps it safe.
History
tracked since 10 Aug 2026 · nothing is ever overwritten
Questions about Urlbox
answered from the record above
Is Urlbox free?
No — the plan we track is $19 a month. Starter at around $19/month billed monthly for a fixed number of screenshots.
Can you replace Urlbox by building your own?
YES. Replaceable in one session with an AI coding agent. Replacement score 87 out of 100, build time one session. Read what you lose before you decide.
How much does Urlbox cost?
$19 a month on Starter — $228 a year. Recorded 10 Aug 2026.
What do you lose by replacing Urlbox?
A fleet of browsers somebody else keeps patched and warm; Handling for cookie banners, lazy-loaded images and web fonts that arrive late; Capacity for a burst of requests without you provisioning anything. If any of those carry weight for you, keep paying.
Is there an open-source alternative to Urlbox?
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

