Skip to content

Field Boundary: Tamer vs Wrangler

The single most important thing to understand when working with Tamer config: some fields are Tamer instructions, some are Wrangler passthrough, and some are shared. Mixing them up causes real bugs (e.g. declaring build on a worker config and having Wrangler re-run the build without env vars).

How fields flow

tamer/project.config.ts (WorkerConfig)


  stripTamerFields()  ── removes Tamer-only fields


  generateWranglerConfig()  ── expands resources into bindings


  wrangler.json (WranglerConfig)

Tamer-only fields are stripped before writing wrangler.json. They never appear in the generated config. Passthrough fields survive the strip and land in wrangler.json verbatim.

Tamer-only fields (never in wrangler.json)

These fields are instructions to Tamer itself. They are stripped by stripTamerFields in src/core/config/resolver.ts and do not appear in the generated wrangler.json.

FieldPurpose
pathResolves the worker's project directory (relative to repo root)
configOptional config override path
resourcesTamer-managed Cloudflare resources (d1, r2, kv, etc.) — expanded into wrangler bindings by resource modules
buildPre-deploy build command (e.g. vite build) — Tamer spawns it with resolved vars as env
secretsDeclares required secret names — values come from the Tamer vault, pushed via CF API
localLocal-env override block
envPer-env override blocks (env.dev, env.prod, etc.)
scriptNameOverrides the generated Wrangler worker name
wranglerOutFileBasename of the generated wrangler JSON (default wrangler.json)
dispatchNamespaceWorker-level override that passes --dispatch-namespace to wrangler deploy (deploys this worker into a WFP namespace). Stack-level namespace declarations live under wfp.namespaces.
doMigrationsAppend-only Durable Object class migrations (wrangler migrations[]). See Durable Objects.
tamerRoutesTamer-managed HTTP routes — expanded per env, applied via Workers Routes API
tamerStaleRouteSweepZonesZones where tamer deploy prunes orphaned routes
aliasBinding name aliases

Passthrough fields (appear in wrangler.json)

These are Wrangler-native fields declared in WorkerConfig via BaseWranglerFields. They flow straight through to the generated wrangler.json.

FieldNotes
varsRuntime Worker environment variables. Also consumed by the build step as compile-time env.
assetsStatic assets configuration (directory, not_found_handling, etc.)
routesWrangler-native static routes (not tamerRoutes)
servicesService bindings
durable_objectsPrefer managed resources.durableObjects + doMigrations (see Durable Objects). Raw passthrough still works when managed fields are absent; tamer deploy topo-sorts on passthrough script_name and managed scriptName.
worker_loadersWorker Loader bindings for Dynamic Workers / DO facets. Passthrough — Tamer does not inventory facets.
aiAI binding
compatibility_dateWrangler compatibility date
compatibility_flagsWrangler compatibility flags
workers_devEnable/disable *.workers.dev subdomain
preview_urlsEnable/disable preview URLs
limitsCPU time, etc.
observabilityLogs, sampling rate
queuesQueue consumers (producers are managed by Tamer resources)
[any other WranglerConfig field]Anything not in the ManagedFields or Tamer-only list passes through

The build field collision

Historical bug (fixed in 0.34.1)

Before 0.34.1, the build field was not stripped from wrangler.json. Wrangler has its own native build.command feature — when it saw build in wrangler.json, it ran vite build itself (without Tamer's injected env vars), overwriting the correct build output. This caused a silent double-build where the second (wrong) build was deployed.

The fix: build is now in the stripTamerFields list. It's a Tamer instruction only. Wrangler never sees it.

Managed fields (replaced, not passed through)

These Wrangler fields are replaced by Tamer's resource modules — you don't declare them directly; they're generated from resources:

Wrangler fieldGenerated from
d1_databasesresources.d1[]
r2_bucketsresources.r2[]
kv_namespacesresources.kv[]
queues (producers)resources.queues[]
hyperdriveresources.hyperdrive[]
vectorizeresources.vectorize[]
workflowsresources.workflows[]
durable_objectsresources.durableObjects[] + worker doMigrations
pipelinesresources.pipelines[]
secrets_store_secretsresources.secretsStoreSecrets[]
nameResolved from scriptName or naming engine

Checking the boundary

If you're unsure whether a field is Tamer-only or passthrough, check stripTamerFields in src/core/config/resolver.ts — that function is the single source of truth for what gets stripped.

Runtime validation (Zod) vs Wrangler types

loadConfig Zod schemas validate Tamer-owned structure (resources, secrets, tamerRoutes, naming, …). They must not re-declare Wrangler passthrough shapes or enums (assets.not_found_handling, route unions, …).

LayerOwns
Generated WranglerConfig (src/generated/…)Compile-time types for passthrough fields
WorkerConfigSchema .passthrough()Keeps unknown Wrangler keys intact at load
Wrangler CLIRuntime validation of emitted wrangler.json

Hand-copied Zod enums for Wrangler fields are a second source of truth and have already drifted (legacy return-404 vs Wrangler 404-page). Do not add them back — extend generated types / let Wrangler reject invalid config.

Released under the Tamer Evaluation License.