Skip to content
RT
Go back

Cloudflare Workers bindings: permission + API in env

Keep the word binding in English.

Calling it a “plug” or “socket” sounds cute and confuses people. A binding is not a cable. In Cloudflare Workers it’s a declared connection between your Worker and a resource.

Cloudflare frames it as permission plus API. In practice:

you configure that a Worker may use a resource, and in code you receive it on env.

env.MY_BUCKET
env.EMAIL_QUEUE
env.COUNTERS
env.AI

If you came from Workers 101, this is the next step: the Worker stops being “just fetch” and becomes a door with capabilities.

The problem it solves

Without bindings, integrations often look like this:

const bucketUrl = "https://...";
const apiKey = "abc...";
const queueName = "prod-email-queue";

That gets fragile:

With bindings, the dependency is declared in the Worker config. Your code doesn’t invent a manual connection to Cloudflare resources—it uses the API on env.

Don’t say / better

Don’t say:

“The binding is a secure plug.”

Better:

“The binding is a declared capability the Worker receives on env.”

Readable architecture example:

Worker "api" has:
- ASSETS_BUCKET
- EMAIL_QUEUE
- USER_COUNTERS

If you draw the bindings, you start to see the system.

Example: signup → Queue

type SignupJob = {
  email: string;
  source: string;
};

export interface Env {
  SIGNUP_QUEUE: Queue<SignupJob>;
}

export default {
  async fetch(request, env): Promise<Response> {
    const body = await request.json<SignupJob>();

    await env.SIGNUP_QUEUE.send({
      email: body.email,
      source: body.source ?? "web",
    });

    return Response.json({ accepted: true }, { status: 202 });
  },
} satisfies ExportedHandler<Env>;

The line that matters:

await env.SIGNUP_QUEUE.send(...);

SIGNUP_QUEUE exists because it was declared as a binding—not because the code “discovered” a magic queue.

Example: R2

R2 is object storage. If the bucket is bound, the Worker uses it directly:

export interface Env {
  ASSETS: R2Bucket;
}

export default {
  async fetch(request, env): Promise<Response> {
    if (request.method !== "PUT") {
      return new Response("Use PUT", { status: 405 });
    }

    const key = new URL(request.url).pathname.slice(1);
    await env.ASSETS.put(key, request.body);

    return Response.json({ ok: true, key });
  },
} satisfies ExportedHandler<Env>;

You’re not inventing an HTTP client with hard-coded tokens. You’re using a capability configured for this Worker in this environment.

Bindings as a design tool

Useful question:

What capabilities does this Worker need?

AppTypical capabilities
Image APIR2 + Queue (thumbnails) + D1 (metadata)
ChatDurable Object (room) + Queue (async analytics)
AI featureAI Gateway + R2 (results) + Queue (long jobs)

If you can’t list the bindings, there’s probably hidden architecture.

Anti-pattern: too many bindings on one Worker

API Worker:
- USERS_DB
- EMAIL_QUEUE
- IMAGE_BUCKET
- CRM_SECRET
- ANALYTICS_QUEUE
- CHAT_ROOM
- AI_GATEWAY
- BILLING_SERVICE

Not always wrong. But ask:

Is this still one clear responsibility?

If not, split. One binding per resource doesn’t justify a god-Worker.

Environments: same name, different resource

The code name can stay EMAIL_QUEUE everywhere. The real resource must change per environment:

EMAIL_QUEUE → prod queue
EMAIL_QUEUE → staging queue
EMAIL_QUEUE → preview queue

Staging shouldn’t write to production by accident. Preview shouldn’t touch the real customer bucket.

How to name them

Good:

RECEIPT_QUEUE
PRODUCT_IMAGES
USER_COUNTERS
AI_GATEWAY

Weak:

DATA
THING
BUCKET2
PROD_STUFF

Binding names are operational docs. Treat them that way.

Checklist before you ship

  1. Every binding has a clear name.
  2. Dev / staging / prod point at the right resources.
  3. Secrets aren’t hard-coded.
  4. The Worker handles resource failures.
  5. Logs say which dependency failed.
  6. The Worker isn’t a responsibility dump.
  7. The Env type is up to date.

The line to remember

binding = permission + API available on env

And the loop:

fetch() receives the request
env brings the configured capabilities
bindings show which resources the Worker uses

Cloudflare series for builders: Workers · Bindings · Durable Objects · Queues

Docs: Bindings · Wrangler config · Environments · Queues · R2 from Workers


Share this post on:

Previous Post
Durable Objects: identity, state, and coordination
Next Post
Cloudflare Workers without the buzzwords