Project Config
Tamer config is a TypeScript file (tamer/project.config.ts) that declares your stack: stack identity, workers, resources, routes, Workers for Platforms tenants, and outputs.
Minimal config
import { defineConfig } from "@dragonmastery/tamer";
export default defineConfig({
stack: "my-app",
account_id: "your-cloudflare-account-id",
compatibility_date: "2025-05-19",
worker: {
main: "src/worker.ts",
scriptName: "my-app",
resources: {
d1: [{ logicalName: "settings", type: "single" }],
},
},
});Multi-worker config
import { defineConfig } from "@dragonmastery/tamer";
import { apiWorker } from "./workers/api/base";
import { spaWorker } from "./workers/spa/base";
export default defineConfig({
stack: "my-app",
account_id: "your-cloudflare-account-id",
compatibility_date: "2025-05-19",
workers: {
api: apiWorker,
spa: spaWorker,
},
outputs: {
api_worker_name: "${tamer:worker:api.name}",
},
});Config location
Tamer discovers config by walking up from the current working directory (like git or npm), so commands work from any subdirectory. It looks for, in order:
tamer/project.config.ts(nested layout — recommended)tamer.project.config.ts(flat layout, at the repo root)
--config <path> points at any .ts file explicitly (any filename except tamer.config.ts, which is rejected — see below).
tamer.config.ts is not supported
A file literally named tamer.config.ts is rejected by the CLI (in both discovery and --config). Move its default export to tamer/project.config.ts (or tamer.project.config.ts at the repo root).
Per-env overlays (optional)
Alongside the project config, Tamer can merge an env overlay for the target --env:
- Nested layout:
tamer/env/<env>.config.ts(next toproject.config.ts) - Flat layout:
tamer.env.<env>.ts(next totamer.project.config.ts)
Use defineProjectOverlay (exported from @dragonmastery/tamer) to author an overlay. The overlay is shallow-merged onto the base project config, so you can override per-env values (vars, routes, naming) without forking the whole stack. The overlay env can also be forced via the TAMER_OVERLAY_ENV_KEY env var.
Splitting config across files
Worker definitions are typically split into base.ts + env/*.ts files:
tamer/
project.config.ts # top-level config, imports workers
workers/
api/
base.ts # base worker config
env/
dev.ts # dev overrides
prod.ts # prod overrides
spa/
base.ts
env/
dev.ts
prod.tsThis keeps env-specific values (API URLs, feature flags) in focused files while the base config declares shared structure (bindings, routes, resources).
Cross-resource references
Embed ${tamer:<kind>:<logicalName>.<field>} to interpolate resolved state values at config generation time:
vars: {
ASSETS_BUCKET: "${tamer:r2:assets.name}",
DB_ID: "${tamer:d1:settings.id}",
}Resolution modes:
- Strict (
apply,deploy,destroy): unresolved refs throw - Tolerant (
plan,drift,status,sync): unresolved refs stay as placeholders
See Values Lifecycle for full details.
The cf binding DSL
Typing ${tamer:r2:assets.name} strings by hand is error-prone (typos in the kind or field aren't caught by the type checker). Tamer exports a cf helper that builds the same reference with IDE autocomplete:
import { cf, defineConfig } from "@dragonmastery/tamer";
export default defineConfig({
// …
workers: {
api: {
vars: {
ASSETS_BUCKET: cf.r2("assets").name, // → ${tamer:r2:assets.name}
DB_ID: cf.d1("settings").id, // → ${tamer:d1:settings.id}
QUEUE_NAME: cf.queue("events").name,
},
},
},
outputs: {
api_worker_name: cf.worker("api").name, // → ${tamer:worker:api.name}
edge_queue: cf.import("net", "edgeQueue"),// → ${tamer:import:net.edgeQueue}
},
});cf.<kind>(<logical>) returns an object with .name, .id, and .binding for every managed kind (d1, r2, kv, queue, hyperdrive, vectorize, aiGateway, pipeline, workflow, secretStore). Plus:
cf.worker(<workerKey>).name— deployed script name for this envcf.dispatchNamespace(<logical>).name | .idcf.import(<stack>, <output>)— cross-stack outputcf.stack.accountIdcf.logpushPipelines(<logical>).*— R2 Data Catalog / Iceberg fields
Both styles produce identical results — cf just materializes to the same ${tamer:…} string the resolver already understands (no second resolution path). cf is recommended for new configs; the raw string form is useful in prose, comments, or non-Tamer files.
Stack outputs
Declare named values your stack publishes after apply:
outputs: {
usersDbId: "${tamer:d1:users.id}",
apiName: "${tamer:worker:api.name}",
}Sibling stacks consume them via ${tamer:import:<stack>.<output>}.