Skip to content

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

ts
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

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

  1. tamer/project.config.ts (nested layout — recommended)
  2. 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 to project.config.ts)
  • Flat layout: tamer.env.<env>.ts (next to tamer.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.ts

This 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:

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

ts
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 env
  • cf.dispatchNamespace(<logical>).name | .id
  • cf.import(<stack>, <output>) — cross-stack output
  • cf.stack.accountId
  • cf.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:

ts
outputs: {
  usersDbId: "${tamer:d1:users.id}",
  apiName: "${tamer:worker:api.name}",
}

Sibling stacks consume them via ${tamer:import:<stack>.<output>}.

Released under the Tamer Evaluation License.