Cloudflare Worker Previews Setup: Which Bindings Isolate Per Branch and Which You Configure Yourself
Cloudflare shipped Worker Previews today, and the headline is easy to state. Every git branch gets its own running Worker, with its own URL, its own config, its own logs, its own traces. You run npx wrangler preview, and the branch you’re sitting on becomes something you can open in a browser and click through. Cloudflare says you can run hundreds of them side by side without them touching each other or production.
If that sounds like the preview URLs Workers already had, it isn’t. Those got renamed in the same announcement. They’re Version URLs now, because that’s what they always were: a link pointing at one uploaded version of your Worker, running against your production bindings. Fine for eyeballing a build. No good for testing a migration, because the database you’d break is the real one.
A Preview is a different object. It’s an environment inside the same Worker, tied to a branch, with a stable URL that sticks around as you push to it. You can put it behind Cloudflare Access if you don’t want it public, and point a custom domain at it, something like feature-login.previews.example.com. The dashboard grows a breadcrumb so you can flip between production and any Preview when you’re reading traces.
What you get without configuring anything
Two things provision themselves per branch.
Durable Objects get a brand new namespace and fresh storage for every Preview. Production code resolving ctx.exports.Counter hits the production namespace. The same line in a Preview hits that Preview’s own. Containers behave the same way, a separate app and separate instances per branch.
That’s the automatic list. Everything else is yours to wire up, which is the next section.
Setting up the previews block
The rest of your bindings are inherited from production unless you tell Wrangler otherwise, and you tell it in a previews block that mirrors the shape of your top-level config and swaps the targets:
{
"vars": { "ENVIRONMENT": "production" },
"r2_buckets": [{ "binding": "UPLOADS", "bucket_name": "prod-uploads" }],
"previews": {
"vars": { "ENVIRONMENT": "preview" },
"r2_buckets": [{ "binding": "UPLOADS", "bucket_name": "r2-staging" }]
}
}
Set that base once and every Preview inherits it. Individual Previews can override on top of it without touching production or each other, so one branch can point at a scratch bucket while the rest share the staging one.
The bindings worth pointing somewhere non-production while you’re in there:
- D1. Two Previews on the same
database_idwrite to the same rows. Give the branches you run migrations on their own database. - R2. Same bucket name means the same objects.
- KV. Isolate by namespace ID.
- Vectorize. Isolate by
index_name. - Hyperdrive. Point it at a separate database or at least a separate schema.
- Analytics Engine. Two Previews writing to one dataset share rows, so your test traffic lands in your real numbers.
- Queue producers. Same queue name, same queue.
Secrets don’t go in the file. They go in through npx wrangler preview secret put.
A few things Cloudflare tells you to keep out of the block: queue consumers, cron triggers, production routes. Assets stay at the top level too.
Do that work and the “a failed migration stays contained to that branch” promise holds. Skip it and a Preview will happily run your destructive test against production data, so it’s worth walking the binding list once before you trust a branch with anything irreversible.
Footnotes on what doesn’t work yet
Service bindings don’t chain. If Worker A’s Preview calls Worker B, it gets production Worker B, not B’s matching Preview. If you run a mesh of small Workers, that’s the one to plan around.
Queue consumers don’t run in a Preview. Only the production consumer picks up messages, so a Preview can produce but not process.
Workflows need a dedicated non-production Workflow deployed by hand. Nothing is provisioned per branch.
Cron triggers don’t fire per Preview, so anything scheduled has to be invoked manually while you’re testing it.
Long-lived Previews, the kind you’d keep as a permanent staging or QA environment, are on the roadmap rather than in the product today.
The agent loop this was built for
Cloudflare isn’t being coy about the motivation. The framing in the announcement is that agents are pushing more code than before, bigger diffs need more ground covered before release, and a reviewer looking at a PR on their phone can’t run the branch.
So the loop they describe runs like this. The agent deploys to a Preview, drives the URL through Playwright MCP, pulls traces back through the Workers Observability MCP server, patches whatever broke, redeploys, checks again. Then a person opens a working URL instead of reading a diff. Ramp is quoted doing roughly that for agent-written PRs, and Supermemory for Durable Object changes. Cloudflare ran it internally on CloudflareOS first.
That also explains why Durable Objects got the automatic treatment. An agent hammering a DO in a shared namespace corrupts state for every other branch at once, so that was the one that couldn’t wait for you to configure it.
Worth setting up. Do the binding pass first.