How to Manage Environment Variables and Secrets in Cloudflare Workers: A Complete Guide
To manage environment variables and secrets in Cloudflare Workers, store local values in .dev.vars, production values via wrangler secret put, and access them type-safely through getCloudflareContext().env using worker-configuration.d.ts for TypeScript definitions.
This guide demonstrates how to manage environment variables and secrets in Cloudflare Workers using the ifindev/fullstack-next-cloudflare template. The repository implements a unified workflow that keeps credentials out of source control while providing compile-time safety through auto-generated TypeScript definitions.
Local Development with .dev.vars
Local preview runs rely on a dedicated environment file that wrangler dev automatically injects into the Worker context.
Setting Up Your Local Environment File
Start by copying the example template included in the repository:
cp .dev.vars.example .dev.vars
Edit .dev.vars and populate it with your Cloudflare account details, database tokens, storage URLs, and OAuth credentials. The example file defines all required keys including CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_D1_TOKEN, CLOUDFLARE_R2_URL, BETTER_AUTH_SECRET, and Google OAuth variables.
Local Development Workflow
When you run wrangler dev, the Wrangler CLI automatically loads variables from .dev.vars and exposes them via the env object. This file is gitignored by default, ensuring your secrets remain local-only.
Managing Production Secrets in Cloudflare Workers
For deployed Workers, Cloudflare Secrets provide encrypted storage that persists across deployments.
Uploading Secrets via CLI
Use the wrangler secret put command to push each variable to your Cloudflare account:
wrangler secret put CLOUDFLARE_ACCOUNT_ID
wrangler secret put CLOUDFLARE_D1_TOKEN
wrangler secret put CLOUDFLARE_R2_URL
wrangler secret put BETTER_AUTH_SECRET
wrangler secret put GOOGLE_CLIENT_ID
wrangler secret put GOOGLE_CLIENT_SECRET
These values become available as env properties inside your Worker, identical to local development. The CI pipeline in .github/workflows/deploy.yml also injects these secrets during preview builds.
Type-Safe Variable Access
The repository implements strict TypeScript safety through auto-generated type definitions.
Using worker-configuration.d.ts
The worker-configuration.d.ts file declares the exact shape of your env object:
// Automatically generated or manually maintained
interface CloudflareEnv {
CLOUDFLARE_ACCOUNT_ID: string;
CLOUDFLARE_R2_URL: string;
next_cf_app_bucket: R2Bucket;
my_d1_database: D1Database;
// ... additional bindings
}
This declaration enables compile-time checking and IntelliSense when accessing env.CLOUDFLARE_ACCOUNT_ID or other variables.
The getCloudflareContext() Pattern
Import the context helper from OpenNext to access environment variables inside API routes or server functions:
import { getCloudflareContext } from "@opennextjs/cloudflare";
export async function someWorkerLogic() {
const { env } = await getCloudflareContext();
// Type-safe access with full autocomplete
const accountId = env.CLOUDFLARE_ACCOUNT_ID;
const r2Url = env.CLOUDFLARE_R2_URL;
}
Practical Implementation Examples
R2 File Upload with Environment Variables
The src/lib/r2.ts file demonstrates consuming CLOUDFLARE_R2_URL to construct public URLs:
import { getCloudflareContext } from "@opennextjs/cloudflare";
export async function uploadToR2(file: File) {
const { env } = await getCloudflareContext();
// Bucket binding defined in wrangler.jsonc
const bucket = env.next_cf_app_bucket;
const key = `uploads/${Date.now()}_${crypto.randomUUID()}.${file.name.split('.').pop()}`;
const arrayBuffer = await file.arrayBuffer();
await bucket.put(key, arrayBuffer, {
httpMetadata: { contentType: file.type },
customMetadata: { originalName: file.name },
});
// Uses the R2 base URL from secrets
const publicUrl = `https://${(env as any).CLOUDFLARE_R2_URL}/${key}`;
return publicUrl;
}
Database Configuration with Drizzle
The drizzle.config.ts file shows how the same variables support both build-time and runtime operations:
import { drizzle } from "drizzle-orm/d1";
import { Database } from "cloudflare:workers";
export async function getDb(env: any) {
const db: Database = env.my_d1_database;
return drizzle(db);
}
During migrations, the config uses process.env.CLOUDFLARE_ACCOUNT_ID and process.env.CLOUDFLARE_D1_TOKEN, while the runtime code accesses env.my_d1_database through the Cloudflare context.
Summary
- Local development uses
.dev.vars(copied from.dev.vars.example) for variable injection duringwrangler dev - Production deployments require
wrangler secret putcommands to encrypt and store sensitive values in Cloudflare's infrastructure - Type safety is enforced through
worker-configuration.d.ts, which maps secret names to theenvobject returned bygetCloudflareContext() - Runtime access occurs exclusively through
getCloudflareContext().env, never throughprocess.envinside the Worker runtime - CI/CD integration in
.github/workflows/deploy.ymlautomates secret injection for preview environments
Frequently Asked Questions
What is the difference between .dev.vars and wrangler secret put?
.dev.vars is a local file used only during development and preview runs, while wrangler secret put uploads encrypted values to Cloudflare's edge infrastructure for production Workers. Both methods expose variables identically through env, but secrets are encrypted at rest and never visible in dashboard logs or source control.
Can I use process.env to access variables in Cloudflare Workers?
No. Inside the Cloudflare Worker runtime, you must use getCloudflareContext().env to access environment variables and secrets. The process.env object is only available during build-time operations like database migrations in drizzle.config.ts, not during request handling.
How do I add type definitions for custom environment variables?
Add declarations to worker-configuration.d.ts. This file generates TypeScript interfaces for the env object, providing compile-time validation and autocomplete for variables like CLOUDFLARE_R2_URL and service bindings like next_cf_app_bucket or my_d1_database.
Where are R2 bucket bindings configured?
R2 bindings are defined in wrangler.jsonc, not in environment variables. While the bucket binding name (next_cf_app_bucket) appears in the env object, the actual connection to your R2 bucket is established through the [[r2_buckets]] section of your Wrangler configuration file.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →