# How to Manage Environment Variables and Secrets in Cloudflare Workers: A Complete Guide

> Master Cloudflare Workers environment variables and secrets. Learn to use .dev.vars, wrangler secret put, and type-safe access for secure and efficient applications.

- Repository: [Muhammad Arifin/fullstack-next-cloudflare](https://github.com/ifindev/fullstack-next-cloudflare)
- Tags: how-to-guide
- Published: 2026-03-03

---

**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`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/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:

```bash
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:

```bash
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`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/.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`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/worker-configuration.d.ts)

The [`worker-configuration.d.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/worker-configuration.d.ts) file declares the exact shape of your `env` object:

```ts
// 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:

```ts
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`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/lib/r2.ts) file demonstrates consuming `CLOUDFLARE_R2_URL` to construct public URLs:

```ts
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`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/drizzle.config.ts) file shows how the same variables support both build-time and runtime operations:

```ts
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 during `wrangler dev`
- **Production deployments** require `wrangler secret put` commands to encrypt and store sensitive values in Cloudflare's infrastructure
- **Type safety** is enforced through [`worker-configuration.d.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/worker-configuration.d.ts), which maps secret names to the `env` object returned by `getCloudflareContext()`
- **Runtime access** occurs exclusively through `getCloudflareContext().env`, never through `process.env` inside the Worker runtime
- **CI/CD integration** in [`.github/workflows/deploy.yml`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/.github/workflows/deploy.yml) automates 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`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/drizzle.config.ts), not during request handling.

### How do I add type definitions for custom environment variables?

**Add declarations to [`worker-configuration.d.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/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.