> ## Documentation Index
> Fetch the complete documentation index at: https://better-router.bansal.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Bindings

> Declare Cloudflare Worker bindings in wrangler.jsonc and read them with getEnv() or c.env.

A **binding** is a value Cloudflare injects onto `env` when the Worker runs. better-router exposes that object on the request:

```ts theme={null}
import { getEnv } from "better-router";
import type { LoaderArgs } from "better-router";

export async function loader({ c }: LoaderArgs) {
  const env = getEnv();
  // same object as c.env
  return { url: env.APP_URL };
}
```

Declare bindings in `wrangler.jsonc`. Type them by augmenting `AppBindings`.

## Binding map

| Binding         | Cloudflare product | Typical `env` name         |
| --------------- | ------------------ | -------------------------- |
| D1              | SQLite database    | `DB`                       |
| KV              | Key-value store    | `KV`                       |
| R2              | Object storage     | `BUCKET`                   |
| Queue producer  | Queues             | `EMAILS`                   |
| Queue consumer  | Queues             | Worker `queue` handler     |
| Email (inbound) | Email Routing      | Worker `email` handler     |
| Cron            | Cron Triggers      | Worker `scheduled` handler |
| Assets          | Static Assets      | `assets.directory`         |
| Vars            | Plain env vars     | `c.env.APP_URL`            |
| Secrets         | Encrypted env vars | `c.env.STRIPE_SECRET`      |

better-router does not wrap these products. Use the Cloudflare APIs (or your own clients) against `getEnv()`.

## D1

```jsonc title="wrangler.jsonc" theme={null}
{
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "app",
      "database_id": "<id>"
    }
  ]
}
```

```ts theme={null}
export async function loader() {
  const { DB } = getEnv() as { DB: D1Database };
  const { results } = await DB.prepare("select id, name from users").all();
  return { users: results };
}
```

Create the database with `npx wrangler d1 create app`.

## KV

```jsonc title="wrangler.jsonc" theme={null}
{
  "kv_namespaces": [
    {
      "binding": "KV",
      "id": "<namespace-id>"
    }
  ]
}
```

```ts theme={null}
const kv = getEnv().KV as KVNamespace;
await kv.put("feature:checkout", JSON.stringify({ enabled: true }), { expirationTtl: 3600 });
const flag = await kv.get("feature:checkout", "json");
```

Create a namespace with `npx wrangler kv namespace create KV`.

## R2

```jsonc title="wrangler.jsonc" theme={null}
{
  "r2_buckets": [
    {
      "binding": "BUCKET",
      "bucket_name": "app-media"
    }
  ]
}
```

```ts theme={null}
const bucket = getEnv().BUCKET as R2Bucket;
await bucket.put("avatars/1.png", file);
const object = await bucket.get("avatars/1.png");
```

Create a bucket with `npx wrangler r2 bucket create app-media`.

## Queues

Queues have two sides:

1. **Producer** — `env.EMAILS.send(body)` from a request or cron
2. **Consumer** — the Worker `queue` handler in `src/worker.ts`

```jsonc title="wrangler.jsonc" theme={null}
{
  "queues": {
    "producers": [{ "binding": "EMAILS", "queue": "emails" }],
    "consumers": [{ "queue": "emails" }]
  }
}
```

```ts theme={null}
const emails = getEnv().EMAILS as Queue;
await emails.send({ to: "ada@example.com" });
```

The consumer is `defineWorker({ queue })` — see [Worker entrypoint](/worker).

## Email (inbound)

[Email Routing](https://developers.cloudflare.com/email-routing/email-workers/) can deliver inbound messages to a Worker. Handle them with `defineWorker({ email })`.

## Cron Triggers

Cron expressions live on the Worker, not in DNS.

```jsonc title="wrangler.jsonc" theme={null}
{
  "triggers": {
    "crons": ["*/5 * * * *", "0 9 * * *"]
  }
}
```

The `scheduled` event’s `cron` string is `event.cron` in `defineWorker({ scheduled })`. Exporting a handler is not enough — Cloudflare only fires alarms listed in Wrangler.

## Assets

The client build is static files. Wrangler serves them in front of the Worker so `/assets/client.js` does not hit Hono.

```jsonc theme={null}
{
  "assets": { "directory": "./dist/client" }
}
```

Files in `public/` are copied by Vite into that directory.

## Vars and secrets

Plain values go in `vars`. Secrets go through `wrangler secret put` and never sit in git.

```jsonc theme={null}
{
  "vars": {
    "APP_URL": "https://example.com"
  }
}
```

```bash theme={null}
npx wrangler secret put STRIPE_SECRET
```

Read them with `getEnv().STRIPE_SECRET`. Locally, put secrets in `.dev.vars` (Wrangler) or `.env` (Vite). See [Environment](/environment).

## Type the env

```ts title="src/env.d.ts" theme={null}
declare module "better-router" {
  interface AppBindings {
    DB: D1Database;
    KV: KVNamespace;
    BUCKET: R2Bucket;
    EMAILS: Queue;
    APP_URL: string;
    STRIPE_SECRET: string;
  }
}
```

Then `getEnv().DB` typechecks in loaders, actions, and middleware.

## Example `wrangler.jsonc`

```jsonc title="wrangler.jsonc" theme={null}
{
  "name": "my-app",
  "compatibility_date": "2026-09-01",
  "compatibility_flags": ["nodejs_compat"],
  "main": "./dist/server/index.js",
  "assets": { "directory": "./dist/client" },
  "vars": {
    "APP_URL": "https://example.com"
  },
  "d1_databases": [
    { "binding": "DB", "database_name": "app", "database_id": "<id>" }
  ],
  "kv_namespaces": [
    { "binding": "KV", "id": "<id>" }
  ],
  "r2_buckets": [
    { "binding": "BUCKET", "bucket_name": "app-media" }
  ],
  "queues": {
    "producers": [{ "binding": "EMAILS", "queue": "emails" }],
    "consumers": [{ "queue": "emails" }]
  },
  "triggers": {
    "crons": ["*/5 * * * *"]
  }
}
```

## Other Cloudflare products

These are available on `c.env` if you bind them:

| Binding         | Use                                                  |
| --------------- | ---------------------------------------------------- |
| Hyperdrive      | Pooled Postgres/MySQL                                |
| Durable Objects | Stateful coordination; read `c.env.COUNTER` yourself |
| Workers AI      | Inference; call `c.env.AI` yourself                  |
| Vectorize       | Vector search                                        |
| Workflows       | Durable multi-step jobs                              |

Add them in Wrangler and type them on `AppBindings` the same way.

See [`examples/cloudflare`](https://github.com/bansal/better-router/tree/main/examples/cloudflare) for D1, a queue producer/consumer, and cron in one app.
