Skip to content
Cloudflare Docs

Environments

An environment is a separate, isolated instance of your Worker application. For example, you can have a staging and a production environment running simultaneously on Cloudflare.

Each environment has its own unique URL and can have different configuration — such as different resources that your bindings connect to, different environment variables, and different secrets. This isolation lets you test changes in one environment without affecting users in another.

You first define environments in Wrangler configuration, and then deploy them either manually or automatically through our Git integration.

Configuration

You define environments in your Wrangler configuration file under the env field:

{
"name": "my-api",
"main": "src/index.js",
"compatibility_date": "2025-08-11",
// Environment-specific configurations
"env": {
"production": {
"vars": {
"ENVIRONMENT": "production",
"API_ENDPOINT": "https://api.example.com",
},
"d1_databases": [
{
"binding": "DB",
"database_name": "my-db-production",
"database_id": "production-db-id",
},
],
"routes": ["example.com/*"],
},
"staging": {
"vars": {
"ENVIRONMENT": "staging",
"API_ENDPOINT": "https://staging.example.com",
},
"d1_databases": [
{
"binding": "DB",
"database_name": "my-db-staging",
"database_id": "staging-db-id",
},
],
},
},
}
Complete configuration example with all binding types

This comprehensive example shows how to configure all available binding types across environments. The inline comments explain isolation requirements for each binding type.

{
"name": "my-worker",
"main": "src/index.js",
"compatibility_date": "2025-08-11",
// Environment-specific configurations
"env": {
"production": {
"name": "my-worker-production",
// Environment variables - can have different values per environment
"vars": {
"API_HOST": "example.com",
"API_ACCOUNT_ID": "example_user",
"SERVICE_X_DATA": {
"URL": "service-x-api.prod.example",
"MY_ID": 123,
},
},
// Routes - must be unique (can't have same route for staging and production)
"route": {
"pattern": "example.org/*",
"zone_name": "example.org",
},
// Service bindings - target specific Worker environments
// Use just the worker name to target its production environment
"services": [
{
"binding": "<BINDING_NAME>",
"service": "<WORKER_NAME>", // Targets the production environment
"entrypoint": "<ENTRYPOINT_NAME>",
},
],
// KV namespaces - use different IDs for each environment to isolate data
"kv_namespaces": [
{
"binding": "<MY_NAMESPACE>",
"id": "<PRODUCTION_KV_ID>"
},
{
"binding": "<BINDING_NAME2>",
"id": "<PRODUCTION_NAMESPACE_ID2>",
},
],
// D1 databases - use different IDs for each environment to isolate data
"d1_databases": [
{
"binding": "<BINDING_NAME>",
"database_name": "<DATABASE_NAME>",
"database_id": "<PRODUCTION_DATABASE_ID>"
},
],
// R2 buckets - use different bucket names for each environment to isolate files
"r2_buckets": [
{
"binding": "<BINDING_NAME1>",
"bucket_name": "<PRODUCTION_BUCKET_NAME>"
},
{
"binding": "<BINDING_NAME2>",
"bucket_name": "<PRODUCTION_BUCKET_NAME2>",
"jurisdiction": "eu",
},
],
// Durable Objects - automatically isolated by environment
"durable_objects": {
"bindings": [
{
"name": "<BINDING_NAME>",
"class_name": "<CLASS_NAME>",
},
{
"name": "MY_DURABLE_OBJECT",
"class_name": "MyDurableObjectClass",
"script_name": "external-worker", // Will become "external-worker-production"
"environment": "production",
},
],
},
// Workflows - must have unique names (account-level resources)
"workflows": [
{
"name": "my-workflow-production", // Must be unique per environment
"binding": "MY_WORKFLOW",
"class_name": "MyWorkflow",
},
],
// Queues - use different queue names for each environment to isolate messages
"queues": {
"producers": [
{
"binding": "<BINDING_NAME>",
"queue": "production-queue"
"delivery_delay": 60,
},
],
"consumers": [
{
"queue": "production-queue", // Must match producer queue name
"max_batch_size": 10,
"max_batch_timeout": 30,
"max_retries": 10,
"dead_letter_queue": "production-queue-dlq",
"max_concurrency": 5,
"retry_delay": 120,
},
],
},
// Analytics Engine - use different dataset names for each environment
"analytics_engine_datasets": [
{
"binding": "<BINDING_NAME>",
"dataset": "<PRODUCTION_DATASET_NAME>"
},
],
// Vectorize - must have unique index names per account
"vectorize": [
{
"binding": "<BINDING_NAME>",
"index_name": "<PRODUCTION_INDEX_NAME>", // Must be unique per environment
},
],
// AI binding - can be shared across environments
"ai": {
"binding": "AI",
},
// Browser binding - can be shared across environments
"browser": {
"binding": "<BINDING_NAME>",
},
// Email bindings - can use different addresses per environment
"send_email": [
{
"name": "<NAME_FOR_BINDING1>",
},
{
"name": "<NAME_FOR_BINDING2>",
"destination_address": "<YOUR_EMAIL>@example.com",
},
{
"name": "<NAME_FOR_BINDING3>",
"allowed_destination_addresses": [
"<YOUR_EMAIL>@example.com",
"<YOUR_EMAIL2>@example.com",
],
},
],
// Hyperdrive - use different IDs for each environment to isolate database connections
"hyperdrive": [
{
"binding": "<BINDING_NAME>",
"id": "<PRODUCTION_HYPERDRIVE_ID>"
},
],
// mTLS certificates - typically different per environment
"mtls_certificates": [
{
"binding": "<BINDING_NAME1>",
"certificate_id": "<PRODUCTION_CERTIFICATE_ID>",
},
],
// Version metadata - can be shared across environments
"version_metadata": {
"binding": "CF_VERSION_METADATA",
},
// Tail consumers - automatically target the specified environment
"tail_consumers": [
{
"service": "tail-worker",
},
],
"triggers": {
"crons": ["* * * * *"],
},
"observability": {
"enabled": true,
"head_sampling_rate": 0.1,
},
},
"staging": {
"name": "my-worker-staging",
// Environment variables - can have different values per environment
"vars": {
"API_HOST": "staging.example.com",
"API_ACCOUNT_ID": "staging_user",
"SERVICE_X_DATA": {
"URL": "service-x-api.dev.example",
"MY_ID": 456,
},
},
// Routes - must be unique
"route": {
"pattern": "staging.example.org/*",
"zone_name": "example.org",
},
// Service bindings - target specific Worker environments
// Must append "-staging" to target a Worker's staging environment
"services": [
{
"binding": "<BINDING_NAME>",
"service": "<WORKER_NAME>-staging", // Targets the staging environment
"entrypoint": "<ENTRYPOINT_NAME>",
},
],
// KV namespaces - SHOULD use different namespace IDs for isolation
"kv_namespaces": [
{
"binding": "<MY_NAMESPACE>",
"id": "<STAGING_KV_ID>"
},
{
"binding": "<BINDING_NAME2>",
"id": "<STAGING_NAMESPACE_ID2>",
},
],
// D1 databases - SHOULD use different database IDs for isolation
"d1_databases": [
{
"binding": "<BINDING_NAME>",
"database_name": "<STAGING_DATABASE_NAME>",
"database_id": "<STAGING_DATABASE_ID>"
"preview_database_id": "<PREVIEW_DATABASE_ID>",
},
],
// R2 buckets - SHOULD use different bucket names for isolation
"r2_buckets": [
{
"binding": "<BINDING_NAME1>",
"bucket_name": "<STAGING_BUCKET_NAME>"
"preview_bucket_name": "<PREVIEW_BUCKET_NAME1>",
},
{
"binding": "<BINDING_NAME2>",
"bucket_name": "<STAGING_BUCKET_NAME2>",
"jurisdiction": "eu",
},
],
// Durable Objects - automatically isolated by environment
"durable_objects": {
"bindings": [
{
"name": "<BINDING_NAME>",
"class_name": "<CLASS_NAME>",
},
{
"name": "MY_DURABLE_OBJECT",
"class_name": "MyDurableObjectClass",
"script_name": "external-worker", // Will become "external-worker-staging"
"environment": "staging",
},
],
},
// Workflows - must have unique names (account-level resources)
"workflows": [
{
"name": "my-workflow-staging", // Must be unique per environment
"binding": "MY_WORKFLOW",
"class_name": "MyWorkflow",
},
],
// Queues - SHOULD use different queue names for isolation
"queues": {
"producers": [
{
"binding": "<BINDING_NAME>",
"queue": "staging-queue"
"delivery_delay": 60,
},
],
"consumers": [
{
"queue": "staging-queue", // Must match producer queue name
"max_batch_size": 5,
"max_batch_timeout": 10,
"max_retries": 5,
"dead_letter_queue": "staging-queue-dlq",
"max_concurrency": 2,
"retry_delay": 60,
},
],
},
// Analytics Engine - use different dataset names for each environment
"analytics_engine_datasets": [
{
"binding": "<BINDING_NAME>",
"dataset": "<STAGING_DATASET_NAME>"
},
],
// Vectorize - index names must be unique per account
"vectorize": [
{
"binding": "<BINDING_NAME>",
"index_name": "<STAGING_INDEX_NAME>", // Must be unique per environment
},
],
// AI binding - can be shared across environments
"ai": {
"binding": "AI",
},
// Browser binding - can be shared across environments
"browser": {
"binding": "<BINDING_NAME>",
},
// Email bindings - can use different addresses per environment
"send_email": [
{
"name": "<NAME_FOR_BINDING1>",
},
{
"name": "<NAME_FOR_BINDING2>",
"destination_address": "test@example.com", // Test address for staging
},
],
// Hyperdrive - SHOULD use different config IDs for database isolation
"hyperdrive": [
{
"binding": "<BINDING_NAME>",
"id": "<STAGING_HYPERDRIVE_ID>"
},
],
// mTLS certificates - typically different per environment
"mtls_certificates": [
{
"binding": "<BINDING_NAME1>",
"certificate_id": "<STAGING_CERTIFICATE_ID>",
},
],
// Version metadata - can be shared across environments
"version_metadata": {
"binding": "CF_VERSION_METADATA",
},
// Tail consumers - automatically target the specified environment
"tail_consumers": [
{
"service": "tail-worker-staging",
},
],
"triggers": {
"crons": ["0 */6 * * *"],
},
"observability": {
"enabled": true,
"head_sampling_rate": 1.0,
},
},
},
}

Required environment properties

Because environments run as isolated copies of your application, all of your bindings and environment variables need to be redefined for each environment. This ensures complete isolation between environments — changes to resources in one environment won't affect another.

Each environment configuration must explicitly define:

  • Environment variables (vars)
  • All resource bindings (D1 databases, KV namespaces, R2 buckets, etc.)
  • Secrets (managed separately via Wrangler or the dashboard)
  • Any other configuration specific to that environment

Deployment workflows

All environments must be defined in your Wrangler configuration file. Once configured, you can deploy environments through two main workflows:

Automated deployment with Git integration

When you connect your repository to Cloudflare, Workers Builds automatically creates isolated preview environments for every pull request and can deploy specific branches to designated environments.

Pull request previews

Every pull request automatically gets its own isolated environment. When you open a PR:

  • Cloudflare automatically builds and deploys your code
  • You receive two URLs:
    • Preview URL: A unique URL for each commit - https://<hash>-<worker-name>.<subdomain>.workers.dev
    • Branch URL: A stable URL that always shows the latest commit on that branch - https://<branch-name>-<worker-name>.<subdomain>.workers.dev

These preview environments are perfect for:

  • Testing changes before merging
  • Sharing work with teammates for review
  • Running integration tests against PR-specific endpoints

Manual deployment

Wrangler

Deploy to specific environments using Wrangler:

Terminal window
npx wrangler deploy --env staging
Terminal window
npx wrangler deploy --env production

API

When using the API, you don't create "environments" as a separate entity. Instead, each environment is deployed as its own Worker script. Cloudflare uses a naming convention where each environment becomes a Worker named <worker-name>-<environment-name>. For example, if your base Worker is named my-api:

  • The production environment deploys as a Worker named my-api
  • The staging environment deploys as a separate Worker named my-api-staging

To deploy environments programmatically using the Cloudflare API, you upload each environment as a separate Worker script and specify tags to identify the relationship:

Deploy to production:

Terminal window
curl -X PUT "https://api.cloudflare.com/client/v4/accounts/{account_id}/workers/scripts/my-api" \
-H "Authorization: Bearer {api_token}" \
-H "Content-Type: application/javascript" \
--data-binary "@worker.js" \
--form 'metadata={"main_module":"worker.js","tags":["service=my-api","environment=production"]}'

Deploy to staging:

Terminal window
curl -X PUT "https://api.cloudflare.com/client/v4/accounts/{account_id}/workers/scripts/my-api-staging" \
-H "Authorization: Bearer {api_token}" \
-H "Content-Type: application/javascript" \
--data-binary "@worker.js" \
--form 'metadata={"main_module":"worker.js","tags":["service=my-api","environment=staging"]}'

The tags identify:

  • service: The base Worker name (same across all environments)
  • environment: The specific environment being deployed

This approach means each environment runs as a completely separate Worker with its own isolated execution context, routes, and bindings. The tagging system helps Cloudflare understand the relationship between these separate Workers for management purposes.

How bindings behave in environments

Each binding type has different requirements for environment isolation. Understanding these patterns helps prevent data leakage between environments.

Use different id values to keep data separate between environments:

{
"env": {
"production": {
"kv_namespaces": [
{
"binding": "<MY_NAMESPACE>",
"id": "<PRODUCTION_KV_ID>",
},
],
},
"staging": {
"kv_namespaces": [
{
"binding": "<MY_NAMESPACE>",
"id": "<STAGING_KV_ID>",
// Warning: Using the production ID would share data
},
],
},
},
}

Use different database_id values to maintain separate data stores:

{
"env": {
"production": {
"d1_databases": [
{
"binding": "<BINDING_NAME>",
"database_name": "<DATABASE_NAME>",
"database_id": "<PRODUCTION_DATABASE_ID>",
},
],
},
"staging": {
"d1_databases": [
{
"binding": "<BINDING_NAME>",
"database_name": "<STAGING_DATABASE_NAME>",
"database_id": "<STAGING_DATABASE_ID>",
// Warning: Using the production ID would share the database
},
],
},
},
}

Use different bucket_name values to use different storage buckets for each environment:

{
"env": {
"production": {
"r2_buckets": [
{
"binding": "<BINDING_NAME1>",
"bucket_name": "<PRODUCTION_BUCKET_NAME>",
},
],
},
"staging": {
"r2_buckets": [
{
"binding": "<BINDING_NAME1>",
"bucket_name": "<STAGING_BUCKET_NAME>",
// Warning: Using the production bucket would share files
},
],
},
},
}

Use different queue names to prevent message mixing:

{
"env": {
"production": {
"queues": {
"producers": [
{
"binding": "<BINDING_NAME>",
"queue": "production-queue",
},
],
},
},
"staging": {
"queues": {
"producers": [
{
"binding": "<BINDING_NAME>",
"queue": "staging-queue",
// Warning: Using the production queue would mix messages
},
],
},
},
},
}

Use different id values to connect to separate databases:

{
"env": {
"production": {
"hyperdrive": [
{
"binding": "<BINDING_NAME>",
"id": "<PRODUCTION_HYPERDRIVE_ID>",
},
],
},
"staging": {
"hyperdrive": [
{
"binding": "<BINDING_NAME>",
"id": "<STAGING_HYPERDRIVE_ID>",
// Warning: Using the production ID would connect to production database
},
],
},
},
}

Use different dataset names to keep metrics separate:

{
"env": {
"production": {
"analytics_engine_datasets": [
{
"binding": "<BINDING_NAME>",
"dataset": "<PRODUCTION_DATASET_NAME>",
},
],
},
"staging": {
"analytics_engine_datasets": [
{
"binding": "<BINDING_NAME>",
"dataset": "<STAGING_DATASET_NAME>",
// Warning: Using the production dataset would mix metrics
},
],
},
},
}

Must use unique pattern values. Each URL pattern can only point to one Worker.

{
"env": {
"production": {
"route": {
"pattern": "example.org/*", // Must be unique across all Workers
"zone_name": "example.org",
},
},
"staging": {
"route": {
"pattern": "staging.example.org/*", // Must be unique across all Workers
"zone_name": "example.org",
},
},
},
}

Must specify the target environment by appending the environment name to the service field.

{
"env": {
"production": {
"services": [
{
"binding": "<BINDING_NAME>",
"service": "<WORKER_NAME>", // Targets production environment of the Worker
},
],
},
"staging": {
"services": [
{
"binding": "<BINDING_NAME>",
"service": "<WORKER_NAME>-staging", // Must append environment name
},
],
},
},
}

Must have unique name values. Workflows are account-level resources that environments reference by name.

{
"env": {
"production": {
"workflows": [
{
"name": "my-workflow-production", // Must be unique per environment
"binding": "MY_WORKFLOW",
"class_name": "MyWorkflow",
},
],
},
"staging": {
"workflows": [
{
"name": "my-workflow-staging", // Must be unique per environment
"binding": "MY_WORKFLOW",
"class_name": "MyWorkflow",
},
],
},
},
}

Must have unique index_name values per account. Cannot be changed after creation.

{
"env": {
"production": {
"vectorize": [
{
"binding": "<BINDING_NAME>",
"index_name": "<PRODUCTION_INDEX_NAME>", // Must be unique per environment
},
],
},
"staging": {
"vectorize": [
{
"binding": "<BINDING_NAME>",
"index_name": "<STAGING_INDEX_NAME>", // Must be unique per environment
},
],
},
},
}

Automatically isolated. Each environment gets its own instances with separate storage:

{
"durable_objects": {
"bindings": [
{
"name": "RATE_LIMITER",
"class_name": "RateLimiter",
// Automatically isolated per environment
},
],
},
}

Automatically isolated. Route logs to the specified Worker:

{
"env": {
"production": {
"tail_consumers": [
{
"service": "log-worker",
},
],
},
"staging": {
"tail_consumers": [
{
"service": "log-worker-staging",
},
],
},
},
}

Can share configuration. Provides access to the same AI models across all environments:

{
"ai": {
"binding": "AI",
},
}

Can share configuration. Provides access to the same browser rendering service:

{
"browser": {
"binding": "<BINDING_NAME>",
},
}

Can share configuration. Can use the same certificate or different ones per environment:

{
"env": {
"production": {
"mtls_certificates": [
{
"binding": "<BINDING_NAME1>",
"certificate_id": "<PRODUCTION_CERTIFICATE_ID>",
},
],
},
"staging": {
"mtls_certificates": [
{
"binding": "<BINDING_NAME1>",
"certificate_id": "<STAGING_CERTIFICATE_ID>",
},
],
},
},
}

Version metadata

Can share configuration. Provides Worker version information:

{
"version_metadata": {
"binding": "CF_VERSION_METADATA",
},
}