| name | add-binding |
| description | Adding a new Cloudflare binding (KV, R2, D1, Durable Object, Queue, secret, or environment variable) to this TanStack Start + Cloudflare Workers project. Use this skill whenever the user asks to add a binding, connect a new Cloudflare service, wire up KV/R2/D1/Queues, add a secret, or set an environment variable. Also trigger when the user says things like "I need caching" (KV), "add file storage" (R2), "set up a queue", "add a secret for my API key", "I need a new D1 database", or "add an env var". This skill covers the exact wrangler.jsonc configuration for each binding type, the cf-typegen step to get TypeScript types, and the usage patterns for accessing bindings via `import { env } from "cloudflare:workers"`.
|
Skill: Add a Cloudflare Binding
Workflow
1. Edit wrangler.jsonc
Add the binding configuration. Each binding type has its own top-level key:
KV Namespace
"kv_namespaces": [
{
"binding": "CACHE",
"id": "<namespace-id>"
}
]
R2 Bucket
"r2_buckets": [
{
"binding": "STORAGE",
"bucket_name": "my-bucket"
}
]
D1 Database
"d1_databases": [
{
"binding": "DB",
"database_name": "my-db",
"database_id": "<database-id>",
"migrations_dir": "drizzle"
}
]
Queue (Producer)
"queues": {
"producers": [
{
"binding": "MY_QUEUE",
"queue": "my-queue-name"
}
]
}
Secret
Secrets are NOT declared in wrangler.jsonc. They are set via CLI:
npx wrangler secret put MY_SECRET
Plain Variable (vars)
"vars": {
"ENVIRONMENT": "production",
"API_BASE_URL": "https://api.example.com"
}
2. Regenerate types
After editing wrangler.jsonc, always run:
npm run cf-typegen
This updates worker-configuration.d.ts so TypeScript knows about the
new binding. For example, after adding an R2 bucket with binding STORAGE,
env.STORAGE will be typed as R2Bucket.
3. Use the binding
Import env from cloudflare:workers in server-side code:
import { env } from "cloudflare:workers"
Usage patterns per binding type:
KV
await env.CACHE.put("user:123", JSON.stringify(userData), {
expirationTtl: 3600,
})
const raw = await env.CACHE.get("user:123")
const data = raw ? JSON.parse(raw) : null
await env.CACHE.delete("user:123")
const list = await env.CACHE.list({ prefix: "user:" })
R2
await env.STORAGE.put("photos/cat.jpg", fileBody, {
httpMetadata: { contentType: "image/jpeg" },
})
const object = await env.STORAGE.get("photos/cat.jpg")
if (object) {
const body = object.body
const contentType = object.httpMetadata?.contentType
}
await env.STORAGE.delete("photos/cat.jpg")
const listed = await env.STORAGE.list({ prefix: "photos/", limit: 100 })
D1 (raw, without Drizzle)
const { results } = await env.DB.prepare(
"SELECT * FROM users WHERE id = ?"
).bind(userId).all()
But prefer the Drizzle wrapper (import { db } from "@/db") for D1.
Queue
await env.MY_QUEUE.send({ type: "email", to: "user@example.com" })
await env.MY_QUEUE.sendBatch([
{ body: { type: "email", to: "a@example.com" } },
{ body: { type: "email", to: "b@example.com" } },
])
4. Create the resource (production)
For new projects, the resource must be created in Cloudflare before deploying:
npx wrangler kv namespace create CACHE
npx wrangler r2 bucket create my-bucket
npx wrangler d1 create my-db
Copy the printed IDs into wrangler.jsonc.
For local dev, most bindings work automatically with the Cloudflare Vite
plugin -- no remote resource needed. R2 and KV use local persistence in
.wrangler/state/.
Reference examples in this template
- D1:
wrangler.jsonc d1_databases + src/db/index.ts
- R2:
wrangler.jsonc r2_buckets + src/routes/api/files.ts
Common mistakes to avoid
- Wrong: Importing
env in a client component. cloudflare:workers
is server-only.
- Wrong: Passing
env as a function argument. Import it where needed.
- Wrong: Using
process.env for bindings. That's Node.js.
- Wrong: Forgetting to run
npm run cf-typegen after editing
wrangler.jsonc. TypeScript won't know about the new binding.
- Wrong: Putting secrets in
wrangler.jsonc. Use wrangler secret put
for production and .dev.vars for local dev.