Resources and isolation
Last updated Sep 22, 2026
Binding reference
Two Previews bound to the same account-level resource ID or name share its data or instances. Bind a Preview to a different resource to isolate it.
Resource bindings
This table shows which resources are automatically provisioned and which require separate resource bindings:
Resource or previews field |
How it behaves | To isolate one Preview |
|---|---|---|
| Durable Objects | Automatically provisions a new Durable Object namespace and storage for each Preview. | Automatic. |
| Containers | Automatically provisions a new container app and container instances for each Preview. | Automatic. |
kv_namespaces |
Binds to a KV namespace by id. Two Previews sharing the same id share namespace data. |
Bind to a different KV namespace. |
d1_databases |
Binds to a D1 database by database_id. Two Previews sharing the same database_id share rows. |
Bind to a different D1 database. |
r2_buckets |
Binds to an R2 bucket by bucket_name. Two Previews sharing the same bucket_name share objects. |
Bind to a different R2 bucket. |
queues.producers |
Binds a Queue producer to a queue by name. Two Previews sharing the same queue name send to the same queue. | Bind to a different queue. |
vectorize |
Binds to a Vectorize index by index_name. Must be created manually before deploying. |
Bind to a different index. |
hyperdrive |
Binds to a Hyperdrive configuration by id. Must be created manually. True data isolation requires a separate config pointing at a separate database or schema. |
Bind to a different Hyperdrive config. |
analytics_engine_datasets |
Writes to the dataset name you specify. Datasets are created implicitly by writes. Two Previews writing to the same dataset share rows. |
Bind to a different dataset name. |
pipelines |
Binds to a Pipeline stream by pipeline ID. Two Previews sharing the same stream send events to the same destination. |
Bind to a different stream. |
workflows |
Binds to an existing Workflow. Calls use that Workflow's deployed code, bindings, and instances. | Bind to a dedicated non-production Workflow. |
secrets_store_secrets |
Binds to a Secrets Store secret by store_id and secret_name. |
Bind to a different store or secret name. |
dispatch_namespaces |
Binds to a Workers for Platforms dispatch namespace by namespace. |
Bind to a different namespace. |
mtls_certificates |
Binds to an mTLS client certificate by certificate_id. |
Bind to a different certificate. |
vpc_services |
Binds to a VPC service by service_id. |
Bind to a different service. |
ratelimits |
Creates a Rate Limiting binding. The runtime binding name comes from name, not binding. |
Use a different namespace_id. |
send_email |
Creates a Send Email binding. The runtime binding name comes from name, not binding. Successful delivery requires valid Email Routing setup. |
Not applicable. |
Durable Objects
Durable Objects use a stateful singleton model: within a namespace, each object ID resolves to one instance and its storage. If Previews shared a namespace, they could read and overwrite the same state. For a class defined in the same Worker without script_name, each Preview automatically gets its own namespace and storage.
State persists across deployments within the same Preview and is deleted when the Preview is deleted.
| Access pattern | Preview configuration | Result |
|---|---|---|
ctx.exports |
Class and migration only | Recommended. Each Preview gets a separate namespace automatically. |
env binding |
Class, migration, and a Preview binding | Use when your code needs env.COUNTER. |
Use ctx.exports (recommended)
Every Durable Object setup requires a Durable Object class exported from your Worker and a migration in your Wrangler config.
With ctx.exports (recommended, requires enable_ctx_exports compatibility flag, enabled by default for compatibility_date 2025-11-17 or later), the migration is enough for automatic Preview isolation:
{
// Set this to today's date
"compatibility_date": "2026-10-27",
"migrations": [
{ "tag": "v1", "new_classes": ["Counter"] }
],
"previews": {}
}
# Set this to today's date
compatibility_date = "2026-10-27"
previews = { }
[[migrations]]
tag = "v1"
new_classes = [ "Counter" ]
Production, feature-login, and redesign each get their own Durable Object namespace and storage. The empty previews block is sufficient because no Preview-specific Durable Object binding is needed.
Use an env binding
If your code uses env.COUNTER, declare the binding in the previews block so the Preview has it:
{
"durable_objects": {
"bindings": [
{ "name": "COUNTER", "class_name": "Counter" }
]
},
"migrations": [
{ "tag": "v1", "new_classes": ["Counter"] }
],
"previews": {
"durable_objects": {
"bindings": [
{ "name": "COUNTER", "class_name": "Counter" }
]
}
}
}
[[durable_objects.bindings]]
name = "COUNTER"
class_name = "Counter"
[[migrations]]
tag = "v1"
new_classes = [ "Counter" ]
[[previews.durable_objects.bindings]]
name = "COUNTER"
class_name = "Counter"
Caution
If you use env.COUNTER without declaring the binding in your previews block, the binding will not exist in the Preview and your Worker can return a 1101 error.
Containers
Containers are backed by Durable Objects. Each Preview gets its own Durable Object namespace, container app, and container instances. When you run npx wrangler preview, Wrangler builds and deploys the image for that Preview. The generated app name includes the Worker, Preview, and class names, such as my-worker_feature-login_MyContainer.
Preview container configuration is not inherited from the top level. If both production and Previews need the container, declare the container in both places:
- Top-level
containersfor production. previews.containersfor Previews.
| Access pattern | Preview configuration | Result |
|---|---|---|
ctx.exports |
Class, SQLite migration, and previews.containers |
Recommended when your code does not need an env binding. |
env binding |
Class, SQLite migration, previews.containers, and Preview Durable Object binding |
Use when your code calls env.MY_CONTAINER. |
Use ctx.exports with Containers
Use this pattern when your Worker accesses the container class through ctx.exports instead of an env binding:
const container = ctx.exports.MyContainer.getByName("tenant-a");
return container.fetch(request);
Declare the SQLite-backed Durable Object migration and the container configuration. Put the container configuration under previews.containers so Wrangler creates a container app for each Preview:
{
// Set this to today's date
"compatibility_date": "2026-10-27",
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["MyContainer"] }
],
"containers": [
{
"class_name": "MyContainer",
"image": "./Dockerfile",
"max_instances": 10,
"instance_type": "basic"
}
],
"previews": {
"containers": [
{
"class_name": "MyContainer",
"image": "./Dockerfile",
"max_instances": 10,
"instance_type": "basic"
}
]
}
}
# Set this to today's date
compatibility_date = "2026-10-27"
[[migrations]]
tag = "v1"
new_sqlite_classes = [ "MyContainer" ]
[[containers]]
class_name = "MyContainer"
image = "./Dockerfile"
max_instances = 10
instance_type = "basic"
[[previews.containers]]
class_name = "MyContainer"
image = "./Dockerfile"
max_instances = 10
instance_type = "basic"
With this setup, production and each Preview use separate Durable Object namespaces and separate container apps. No previews.durable_objects binding is needed unless your Worker code reads the container namespace from env.
Use an env container binding
If your code uses an env binding, declare the Durable Object binding at the top level for production and again under previews.durable_objects.bindings for Previews:
import { getContainer } from "@cloudflare/containers";
const container = getContainer(env.MY_CONTAINER, "tenant-a");
return container.fetch(request);
{
// Set this to today's date
"compatibility_date": "2026-10-27",
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["MyContainer"] }
],
"containers": [
{
"class_name": "MyContainer",
"image": "./Dockerfile",
"max_instances": 10,
"instance_type": "basic"
}
],
"durable_objects": {
"bindings": [
{ "name": "MY_CONTAINER", "class_name": "MyContainer" }
]
},
"previews": {
"containers": [
{
"class_name": "MyContainer",
"image": "./Dockerfile",
"max_instances": 10,
"instance_type": "basic"
}
],
"durable_objects": {
"bindings": [
{ "name": "MY_CONTAINER", "class_name": "MyContainer" }
]
}
}
}
# Set this to today's date
compatibility_date = "2026-10-27"
[[migrations]]
tag = "v1"
new_sqlite_classes = [ "MyContainer" ]
[[containers]]
class_name = "MyContainer"
image = "./Dockerfile"
max_instances = 10
instance_type = "basic"
[[durable_objects.bindings]]
name = "MY_CONTAINER"
class_name = "MyContainer"
[[previews.containers]]
class_name = "MyContainer"
image = "./Dockerfile"
max_instances = 10
instance_type = "basic"
[[previews.durable_objects.bindings]]
name = "MY_CONTAINER"
class_name = "MyContainer"
Caution
