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.
| Field | Purpose |
|---|---|
path | Resolves the worker's project directory (relative to repo root) |
config | Optional config override path |
resources | Tamer-managed Cloudflare resources (d1, r2, kv, etc.) — expanded into wrangler bindings by resource modules |
build | Pre-deploy build command (e.g. vite build) — Tamer spawns it with resolved vars as env |
secrets | Declares required secret names — values come from the Tamer vault, pushed via CF API |
local | Local-env override block |
env | Per-env override blocks (env.dev, env.prod, etc.) |
scriptName | Overrides the generated Wrangler worker name |
wranglerOutFile | Basename of the generated wrangler JSON (default wrangler.json) |
dispatchNamespace | Worker-level override that passes --dispatch-namespace to wrangler deploy (deploys this worker into a WFP namespace). Stack-level namespace declarations live under wfp.namespaces. |
doMigrations | Append-only Durable Object class migrations (wrangler migrations[]). See Durable Objects. |
tamerRoutes | Tamer-managed HTTP routes — expanded per env, applied via Workers Routes API |
tamerStaleRouteSweepZones | Zones where tamer deploy prunes orphaned routes |
alias | Binding 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.
| Field | Notes |
|---|---|
vars | Runtime Worker environment variables. Also consumed by the build step as compile-time env. |
assets | Static assets configuration (directory, not_found_handling, etc.) |
routes | Wrangler-native static routes (not tamerRoutes) |
services | Service bindings |
durable_objects | Prefer 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_loaders | Worker Loader bindings for Dynamic Workers / DO facets. Passthrough — Tamer does not inventory facets. |
ai | AI binding |
compatibility_date | Wrangler compatibility date |
compatibility_flags | Wrangler compatibility flags |
workers_dev | Enable/disable *.workers.dev subdomain |
preview_urls | Enable/disable preview URLs |
limits | CPU time, etc. |
observability | Logs, sampling rate |
queues | Queue 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 field | Generated from |
|---|---|
d1_databases | resources.d1[] |
r2_buckets | resources.r2[] |
kv_namespaces | resources.kv[] |
queues (producers) | resources.queues[] |
hyperdrive | resources.hyperdrive[] |
vectorize | resources.vectorize[] |
workflows | resources.workflows[] |
durable_objects | resources.durableObjects[] + worker doMigrations |
pipelines | resources.pipelines[] |
secrets_store_secrets | resources.secretsStoreSecrets[] |
name | Resolved 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, …).
| Layer | Owns |
|---|---|
Generated WranglerConfig (src/generated/…) | Compile-time types for passthrough fields |
WorkerConfigSchema .passthrough() | Keeps unknown Wrangler keys intact at load |
| Wrangler CLI | Runtime 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.