Skip to content

Environment Typing

Cloudflare Workers receive environment bindings (KV namespaces, D1 databases, R2 buckets, queues, secrets, and more) at runtime. Stratal provides a typed StratalEnv interface so these bindings are type-safe throughout your application, from the worker entry point all the way down to individual services.

Stratal defines a base interface with two required bindings:

interface StratalEnv {
ENVIRONMENT: string
CACHE: KVNamespace
}
BindingTypeDescription
ENVIRONMENTstringThe current environment name (e.g., development, production)
CACHEKVNamespaceA KV namespace used by the built-in caching system

Your application will almost certainly need more bindings than this. You extend StratalEnv using TypeScript module augmentation.

The recommended approach is to auto-generate your binding types from your Wrangler configuration (wrangler.toml or wrangler.jsonc) using the Wrangler CLI, then extend them in your StratalEnv declaration.

Step 1: Generate types from your Wrangler config

Section titled “Step 1: Generate types from your Wrangler config”

Run the Wrangler CLI to generate a worker-configuration.d.ts file in your project root:

Terminal window
npx wrangler types

This reads your Wrangler configuration file and generates an interface with all your bindings:

// worker-configuration.d.ts (auto-generated)
declare namespace Cloudflare {
interface Env {
ENVIRONMENT: string
JWT_SECRET: string
CACHE: KVNamespace
DATABASE: D1Database
STORAGE_BUCKET: R2Bucket
NOTIFICATIONS_QUEUE: Queue
}
}

Step 2: Extend StratalEnv from the generated types

Section titled “Step 2: Extend StratalEnv from the generated types”

Use module augmentation to make StratalEnv extend the generated Cloudflare.Env interface:

src/types/env.ts
export {}
declare module 'stratal' {
interface StratalEnv extends Cloudflare.Env {}
}

This pulls in every binding from worker-configuration.d.ts automatically. When you add a new binding to your Wrangler config and re-run npx wrangler types, your StratalEnv picks it up without any manual changes.

Import the declaration file early in your worker entry point:

src/index.ts
import './types/env'
import { Stratal } from 'stratal'
import { AppModule } from './app.module'
export default new Stratal({ module: AppModule })

After this, every service that injects StratalEnv gets full autocompletion and type checking for all bindings defined in your Wrangler configuration.

If you have bindings that are not defined in your Wrangler configuration (for example, bindings injected at deploy time or via secrets), you can still declare them manually:

src/types/env.ts
export {}
declare module 'stratal' {
interface StratalEnv extends Cloudflare.Env {
// Bindings not in wrangler.toml
DEPLOY_SHA: string
FEATURE_FLAGS: KVNamespace
}
}

TypeScript module augmentation merges your custom bindings into the base StratalEnv interface. After augmentation, the effective type becomes:

interface StratalEnv {
// From stratal core
ENVIRONMENT: string
CACHE: KVNamespace
// From your declaration
DATABASE: D1Database
STORAGE_BUCKET: R2Bucket
NOTIFICATIONS_QUEUE: Queue
JWT_SECRET: string
}

This merged type is used everywhere: the DI container, the Hono router bindings, and any service that injects the environment.

Use DI_TOKENS.CloudflareEnv to inject the typed environment into any service:

import { Transient, inject, DI_TOKENS } from 'stratal/di'
import type { StratalEnv } from 'stratal'
@Transient()
export class StorageService {
private readonly bucket: R2Bucket
constructor(
@inject(DI_TOKENS.CloudflareEnv) private readonly env: StratalEnv,
) {
this.bucket = env.STORAGE_BUCKET // fully typed
}
async upload(key: string, data: ReadableStream): Promise<void> {
await this.bucket.put(key, data)
}
}

The environment is registered as a singleton value in the global DI container. It is available in every scope (singleton, transient, and request-scoped).

Cloudflare’s ExecutionContext is also available through DI. This is useful for waitUntil calls or other runtime APIs:

import { Transient, inject, DI_TOKENS } from 'stratal/di'
@Transient()
export class BackgroundTaskService {
constructor(
@inject(DI_TOKENS.ExecutionContext) private readonly ctx: ExecutionContext,
) {}
runInBackground(task: Promise<void>): void {
this.ctx.waitUntil(task)
}
}

When using queues, you need to augment the QueueNames interface so the queue system knows which queues exist. You can derive queue names directly from your environment bindings using TypeScript utility types:

src/types/queues.ts
export {}
/** Extract only the queue binding keys from Cloudflare.Env */
type QueueBindingKeys = {
[K in keyof Cloudflare.Env]: Cloudflare.Env[K] extends Queue ? K : never
}[keyof Cloudflare.Env]
/** Convert a binding name like NOTIFICATIONS_QUEUE to kebab-case: notifications-queue */
type BindingToQueueName<T extends string> =
T extends `${infer Part}_${infer Rest}`
? `${Lowercase<Part>}-${BindingToQueueName<Rest>}`
: Lowercase<T>
/** Derive all queue names from Cloudflare.Env bindings */
type DerivedQueueNames = BindingToQueueName<QueueBindingKeys>
declare module 'stratal' {
interface QueueNames extends Record<DerivedQueueNames, true> {}
}

This derives queue names from your Cloudflare.Env bindings automatically. A binding named NOTIFICATIONS_QUEUE produces the queue name notifications-queue. A binding named ORDER_EVENTS_QUEUE produces order-events-queue.

The queue provider performs the reverse conversion at runtime: it takes a queue name like notifications-queue, converts it to NOTIFICATIONS_QUEUE via .toUpperCase().replace(/-/g, '_'), and looks up the binding in the environment.

Your Wrangler configuration file (wrangler.toml or wrangler.jsonc) is the source of truth for bindings. Running npx wrangler types keeps your types in sync:

[vars]
ENVIRONMENT = "development"
JWT_SECRET = "dev-secret"
[[kv_namespaces]]
binding = "CACHE"
id = "abc123"
[[d1_databases]]
binding = "DATABASE"
database_name = "my-app"
database_id = "def456"
[[r2_buckets]]
binding = "STORAGE_BUCKET"
bucket_name = "uploads"
[[queues.producers]]
binding = "NOTIFICATIONS_QUEUE"
queue = "notifications"

When you add or remove a binding in your Wrangler configuration, re-run npx wrangler types to regenerate the Cloudflare.Env interface. Your StratalEnv declaration picks up the changes automatically.

For larger projects, split your type declarations by feature:

src/
types/
env.ts # Core bindings (DATABASE, secrets)
queues.ts # Queue names and bindings
storage.ts # R2 and KV bindings

Import all of them in your worker entry point:

src/index.ts
import './types/env'
import './types/queues'
import './types/storage'
import { Stratal } from 'stratal'
import { AppModule } from './app.module'
export default new Stratal({ module: AppModule })

Here is how the environment flows from the Cloudflare runtime to your services: