Skip to main content

Bindings and Secrets

In a traditional Node.js environment, your application connects to a database by importing an SDK, reading a .env file for a DB string, and opening a socket connection.

Cloudflare Workers utilize Bindings. A binding is a secure, native link between your Worker code and a Cloudflare resource (like KV, D1, or R2). It grants your script native, zero-latency access without SDKs or connection strings.

Learning Focus

By the end of this module, you will understand how to link external resources via the wrangler.toml file, how to define TypeScript interfaces for your env object, and how to safely store API keys.


1. Environment Variables (Plaintext)

Plain text configurations shouldn't be hardcoded into your TypeScript. They should be defined in wrangler.toml so they can differ between environments (e.g., staging vs. production).

wrangler.toml
[vars]
API_DEBUG_MODE = "true"
SUPPORT_EMAIL = "hello@example.com"

In your Worker, these variables exist on the env object:

src/index.ts
export default {
async fetch(request, env, ctx) {
if (env.API_DEBUG_MODE === "true") {
console.log("Debug mode is active");
}
return new Response(`Contact us at ${env.SUPPORT_EMAIL}`);
}
};

2. Secrets (Encrypted)

Never paste API keys, database passwords, or JWT secrets into wrangler.toml. Cloudflare handles encrypted data via Secrets.

Setting Secrets in Production

Secrets are managed entirely via the Wrangler CLI. They are stored securely on Cloudflare's servers:

npx wrangler secret put STRIPE_API_KEY

The terminal will prompt you to securely paste the secret value.

Setting Secrets in Local Dev

For local development using wrangler dev, the CLI cannot access the production secrets. You must define them in a special .dev.vars file in your project root.

.dev.vars
STRIPE_API_KEY="sk_test_12345"

(Always ensure .dev.vars is added to your .gitignore!)

Accessing Secrets in Code

Secrets are accessed exactly like plaintext environment variables:

export default {
async fetch(request, env, ctx) {
// env.STRIPE_API_KEY is available securely here
const auth = request.headers.get("Authorization");

if (auth !== `Bearer ${env.STRIPE_API_KEY}`) {
return new Response("Unauthorized", { status: 401 });
}
}
};

3. Resource Bindings

To use a KV namespace or D1 database, you must explicitly bind it. This grants your script permission and assigns it a variable name.

Creating the Resource via CLI

First, you must actually create the database on your account:

# Create a KV Namespace
npx wrangler kv:namespace create "MY_CACHE_KV"

# Creating a D1 Database
npx wrangler d1 create "my_sql_db"

Binding the Resource in wrangler.toml

When you create the resource, Wrangler will output a block of configuration showing you the exact ID. Paste this into your wrangler.toml.

wrangler.toml
# Binding a KV Namespace
[[kv_namespaces]]
binding = "CACHE_DB" # The variable name in your JS code
id = "xxxx_1234_yyyy" # The production ID given by the CLI

# Binding a D1 Database
[[d1_databases]]
binding = "MAIN_DB" # The variable name in your JS code
database_name = "my_sql_db"
database_id = "zzzz_5678_wwww"

Accessing the Bindings in Code

export default {
async fetch(request, env, ctx) {
// Access KV via env.CACHE_DB
await env.CACHE_DB.put("user", "Rez");

// Access D1 via env.MAIN_DB
const { results } = await env.MAIN_DB.prepare("SELECT * FROM USERS").all();

return Response.json(results);
}
}

4. TypeScript Interface Pattern

To ensure type-safety, it is crucial to explicitly define the structure of your Env interface at the top of your index.ts file.

Without this, TypeScript won't know that env.MAIN_DB is a D1 Database.

src/index.ts
// 1. Define the Environmental bindings explicitly
export interface Env {
// Plain text variable
API_DEBUG_MODE: string;

// Secret
STRIPE_API_KEY: string;

// Cloudflare Binding Types
CACHE_DB: KVNamespace;
MAIN_DB: D1Database;
USER_BUCKET: R2Bucket;
}

export default {
// 2. Pass the interface into the env argument
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {

// Auto-complete and type-checking will now work perfectly!
const key = env.STRIPE_API_KEY;
await env.CACHE_DB.put("test", "data");

return new Response("Success");
}
};

This pattern ensures that anyone looking at your codebase instantly knows what infrastructure the Worker relies on to function.


What's Next

Writing if (request.url === "/something") is fine for simple scripts, but for robust APIs, you need a router.

Continue to Module 7: Frameworks and Hono to learn the modern standard for writing complex routing logic at the edge.