Guide ·

How to Take Screenshots with Cloudflare Browser Run (2026)

Browser Run is the new name for Cloudflare Browser Rendering (renamed on April 15, 2026): a headless Chrome on Cloudflare's network that you call with one HTTP request or from a Worker. Below is a request that works, how to tell a real PNG from an error, and what to change when the image comes back blank, cut off or covered.

Disclosure: SnapshotFlow publishes this blog and sells a screenshot API, compared briefly at the end. Cloudflare facts come from its docs and changelog, checked on November 4, 2026.

Quick start: your first screenshot with curl

You need your Account ID from the Cloudflare dashboard and a custom API token with the Browser Run - Edit permission. Then:

curl -sS -X POST "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/browser-run/screenshot" \ -H "Authorization: Bearer $CF_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/", "viewport": { "width": 1280, "height": 720 }, "screenshotOptions": { "fullPage": true }, "gotoOptions": { "waitUntil": "networkidle0", "timeout": 45000 } }' \ -D headers.txt \ -o screenshot.png \ -w "HTTP %{http_code} | %{content_type}\n"

This follows Cloudflare's full-page example, with two changes: waitUntil is set to networkidle0 instead of the default domcontentloaded (more on that below), and response headers are saved to headers.txt so you can check the result and the billed browser time. Some Cloudflare examples still use the old /browser-rendering/screenshot path; new code should use /browser-run/.

Check that you got an image, not an error

curl saves whatever comes back to screenshot.png, including a JSON error. Three quick checks:

# 1. The -w line printed by curl: you want HTTP 200 and an image content type # e.g. "HTTP 200 | image/png" # 2. What the file really is file screenshot.png # 3. Billed browser time for this request, in milliseconds grep -i "x-browser-ms-used" headers.txt

If the status is not 200 or the content type is JSON, run cat screenshot.png to read the error message. The most common ones are a token without the Browser Run - Edit permission, a quality value sent with PNG (400), and the two kinds of 429 covered in fix it by symptom. Keep the X-Browser-Ms-Used value: you will need it for the cost estimate.

The same screenshot from a Worker

Inside a Worker you do not need an API token. Add a browser binding named BROWSER in wrangler.json, use a compatibility date of 2026-03-24 or later, and call quickAction:

// wrangler.json: "browser": { "binding": "BROWSER" } export default { async fetch(request, env) { const target = new URL(request.url).searchParams.get("url"); if (!target) return new Response("Missing ?url=", { status: 400 }); return env.BROWSER.quickAction("screenshot", { url: target, screenshotOptions: { fullPage: true, type: "webp" }, gotoOptions: { waitUntil: "networkidle2" }, }); }, };

Before you deploy this: a public Worker that screenshots any ?url= lets anyone spend your browser hours. Add authentication and an allowlist of hosts. quickAction also does not run in local development; use npx wrangler dev --remote.

Fix it by symptom

Find what you see in the image or the response, then apply the change. Each snippet is a fragment of the JSON body from the quick start.

A spinner, skeleton or empty dashboard is in the image

The default waitUntil is domcontentloaded, which fires before API data and many images arrive. The most reliable fix is to wait for an element that appears only after the data has loaded, such as a table row or a chart, instead of guessing a delay:

"waitForSelector": { "selector": "[data-testid='orders-table'] tbody tr", "visible": true, "timeout": 30000 }, "gotoOptions": { "waitUntil": "networkidle2" }

Replace the selector with one from your page. Navigation and selector waits are each capped at 60 seconds. The same problem in Puppeteer is covered in Puppeteer screenshot blank or cut off.

Images at the bottom of a full-page capture are missing

fullPage captures the whole height but does not scroll like a person, so images that load on scroll stay empty. Add scrollPage:

"screenshotOptions": { "fullPage": true }, "scrollPage": true

More on lazy loading in full-page screenshots.

400 error after you set quality

PNG is the default and has no quality setting. Set the type too:

"screenshotOptions": { "type": "webp", "quality": 80 }

WebP is usually the smallest; see WebP screenshots.

429 Too many requests

You sent requests faster than the per-second limit (1 every 10 seconds on Workers Free, 30 per second on Workers Paid). Cloudflare's troubleshooting guide says to read the Retry-After header, wait that long and retry:

const res = await fetch(endpoint, { method: "POST", headers, body }); if (res.status === 429) { const wait = Number(res.headers.get("Retry-After") ?? 1); await new Promise((r) => setTimeout(r, wait * 1000)); // retry the same request }

For steady volume, queue the captures instead of firing them at once; see our batch screenshot guide.

429 Browser time limit exceeded for today

Different cause, different fix. The Workers Free plan's 10 minutes of browser time per day are used up, and retrying will not help: the error lasts until the next UTC day. Move to Workers Paid to continue sooner, and redeploy your Worker if you just upgraded and still see it.

The same image comes back after the page changed

Identical requests are cached for cacheTTL seconds, 5 by default. For monitoring, pass ?cacheTTL=0 in the URL. For link previews that hit the same page all day, raise it (max 86,400) so you do not re-render every time.

A block page or challenge instead of the site

Browser Run adds headers you cannot remove, including Web Bot Auth signatures, and the docs say changing userAgent does not bypass bot protection. Sites that block automated traffic will block these requests. If the site is yours, Cloudflare suggests a WAF skip rule. See also why some websites blur or block screenshots.

A cookie banner covers the page

There is no single option for this. You hide the banner with CSS and block its script, per consent tool. For example, for a site that uses OneTrust:

"addStyleTag": [{ "content": "#onetrust-consent-sdk { display: none !important; } body { overflow: auto !important; }" }], "rejectRequestPattern": ["/cdn\\.cookielaw\\.org/"]

That works for sites you know. When the URLs come from your users, see the last section.

Screenshot options cheat sheet

GoalBrowser Run optionNote
Full scrollable pagescreenshotOptions.fullPageAdd scrollPage for lazy images
Viewport and sharpnessviewport.width, height, deviceScaleFactorDefault 1920×1080; use 2 for crisp text
Format and qualityscreenshotOptions.type (png, jpeg, webp), qualityquality with PNG returns 400
One elementselectorCSS selector
Wait for the pagegotoOptions.waitUntil, waitForSelector, waitForTimeoutDefault domcontentloaded
Logged-in pagescookies, authenticate, setExtraHTTPHeadersBasic auth or tokens
Block requestsrejectResourceTypes, rejectRequestPatternRegex patterns
Change the pageaddStyleTag, addScriptTagHide overlays, freeze animations
Transparent backgroundscreenshotOptions.omitBackgroundUseful with html input
CachecacheTTL query parameterDefault 5 s, max 86,400 s, 0 disables
Keep going on slow eventsbestAttempt: trueContinues when awaited events time out

Browser Run limits in November 2026

LimitWorkers FreeWorkers Paid
Browser time10 minutes per day10 hours per month included, then $0.09 per hour
Quick Action requests1 every 10 seconds30 per second
Concurrent browsers (sessions only)3200 per account (10 included, then $2 each)
Navigation timeout30 s default, 60 s max
After the daily free limit429 until the next UTC dayNot applicable

These have moved a lot this year: the April rename post raised Workers Paid concurrency from 30 to 120 and mentioned a REST limit increase from 3 to 10 requests per second; the current limits page lists 200 and 30. Paid limits can be raised further on request.

Estimated monthly cost

Quick Actions are billed for browser time only. Cloudflare totals usage in seconds and rounds the month to the nearest hour; requests that fail with a waitForTimeout error are not charged. Browser time depends on the page, so the table uses two scenarios, 5 and 10 seconds per capture. Swap in the X-Browser-Ms-Used value from your own headers: monthly hours = captures × ms ÷ 3,600,000.

Monthly screenshots1,00010,00050,000
Browser Run, 5 s per capture$5.00 (1 h, inside 10 included)$5.36 (14 h)$10.31 (69 h)
Browser Run, 10 s per capture$5.00 (3 h)$6.62 (28 h)$16.61 (139 h)
Browser Run, Workers Free$0, if spread to about 120 a day (estimate at 5 s each)Not possibleNot possible
SnapshotFlow (per successful capture)$15 (Basic, 2,000 included)$69 (Growth)$249 (Scale)

Workers Paid rows: $5 monthly minimum plus $0.09 per browser hour above 10. Browser Sessions (Puppeteer, Playwright, CDP) also pay for concurrent browsers above 10. SnapshotFlow prices from our pricing page.

For pages that load cleanly, Browser Run costs a fraction of any per-screenshot API, ours included. If that is your workload, it is the better deal.

When the URLs are other people's sites

The picture changes when users paste links to sites you do not control: link previews, competitor monitoring, reports, evidence. Each site uses its own consent tool, overlay and ad stack, and the addStyleTag and rejectRequestPattern rules from the troubleshooting section become a list you maintain per site.

Here is the same page, IKEA Germany's homepage, captured twice with SnapshotFlow on November 4, 2026. The only difference between the two requests is block_cookie_banners=true&block_ads=true. The IKEA logo and product photos are blurred in both images. (We did not run this comparison on Browser Run.)

IKEA Germany homepage screenshot, logo and photos blurred, with a large cookie consent dialog covering the left half of the page
Default request: the consent dialog covers the hero.
The same IKEA Germany homepage screenshot, logo and photos blurred, with the cookie dialog removed
With block_cookie_banners=true: no site-specific selectors.
curl --get "https://api.snapshotflow.com/screenshot" \ -H "X-Api-Key: $SNAPSHOTFLOW_API_KEY" \ --data-urlencode "url=https://www.ikea.com/de/de/" \ --data-urlencode "format=webp" \ --data-urlencode "block_cookie_banners=true" \ --data-urlencode "block_ads=true" \ -o ikea.webp

Banner blocking uses Ghostery's cookie lists, so it covers the common consent tools, not every overlay. A fundraising popup or a newsletter modal is not a cookie banner; for those there is hide_selectors. What changes is how much of that list you maintain yourself.

 Browser RunSnapshotFlow
Cookie banners, ads, trackersYour own CSS and request patterns, per siteblock_cookie_banners, block_ads, block_trackers
Default waitdomcontentloadednetworkidle2
Text for an LLM with the imageSeparate /markdown call, or /snapshot for HTML + screenshotextract_content=true with response_type=base64: image and Markdown in one JSON
Several URLs, background jobsOne request per URLPOST /batch (up to 10), async=true + webhook_url
Visual changes between capturesBuild it yourselfGET /diff (Growth and Scale)
Billing and throughputBrowser time; 30 requests per secondPer successful capture, cache hits free; 40 to 150 per minute

Pick Browser Run if you are on Workers, capture your own or known pages, or need the lowest bill at volume; for agents that log in and click, compare cloud browsers in Browserbase alternatives. Pick SnapshotFlow if screenshots of third-party sites are a product feature and you would rather not maintain banner rules, batching, webhooks and change detection yourself.

Moving a request over is mostly renaming: screenshotOptions.fullPage → full_page, type → format, viewport.* → width, height, device_scale_factor, waitForSelector → wait_for_selector, addStyleTag → styles or hide_selectors, setExtraHTTPHeaders → headers, cacheTTL=0 → cache=false. The full list is in the API docs.

FAQ

Is Cloudflare Browser Rendering the same as Browser Run?

Yes. Cloudflare renamed Browser Rendering to Browser Run on April 15, 2026. The documented REST path is now /browser-run/screenshot, while some examples and the API reference still use /browser-rendering/. The API token needs the Browser Run - Edit permission.

Is Cloudflare Browser Run free?

There is a free allowance. Workers Free includes 10 minutes of browser time per day and one Quick Action request every 10 seconds; after the daily limit you get a 429 error until the next UTC day. Workers Paid has a $5 monthly minimum, includes 10 browser hours a month and charges $0.09 for each extra hour.

How much does one Browser Run screenshot cost?

Quick Actions are billed by browser time, not per request. If a capture uses 5 seconds of browser time, one browser hour covers 720 captures, so each screenshot beyond the included 10 hours costs about $0.000125. Each response includes an X-Browser-Ms-Used header with the real figure for your page.

Why is my Browser Run screenshot blank or half-loaded?

The default gotoOptions.waitUntil is domcontentloaded, which fires before many images, fonts and client-side data have loaded. Use waitForSelector on an element that appears only after the data loads, or set waitUntil to networkidle0 or networkidle2. For missing images at the bottom of full-page captures, add scrollPage.

How do I fix a 429 error from Browser Run?

There are two kinds. 429 Too many requests means you sent requests too fast: read the Retry-After header, wait that long and retry. 429 Browser time limit exceeded for today means the Workers Free daily limit is used up: it lasts until the next UTC day, or until you move to Workers Paid.

Sources

Checked on November 4, 2026. Cloudflare has changed Browser Run limits several times this year, so confirm before you commit.

Screenshots of third-party sites

Try your URL in the playground

Paste the page that gave you a banner-covered or half-loaded capture and toggle cookie-banner and ad blocking. If the result is what your product needs, the same request runs from your code with 300 free screenshots and no card.

  • Cookie-banner, ad and tracker blocking with one parameter each
  • PNG, JPEG, WebP or PDF; full page or one element
  • Markdown in the same response, batches of 10, webhooks and pixel diffs
  • Pay only for successful captures; cache hits are free