Skip to content

Naming Conventions ​

How Tamer derives Cloudflare names from your config, and how to override them for brownfield stacks where the legacy names don't follow the default pattern.

Env placement: prefix vs suffix ​

Tamer uses an intentional asymmetry so URLs read left-to-right as {env}.{service}.{zone} while Cloudflare object names stay immutable-friendly with a trailing -{env} / _{env}.

SurfaceEnv placementExamples
HTTP hostnames (tamerRoutes)PREFIX {env}.{apex}dev.ca.example.com, pr-1234.api.example.com
Prod HTTP hostnamesBare apex (no env label)ca.example.com
DNS record namesOperator-controlled (usually prefix via ${tamer:env}.…)*.${tamer:env}.dev.todo.com → *.pr-7.dev.todo.com
Email routing/sending hostsPREFIX {env}.post.<zone> (literal in config; prod bare)dev.post.aat.example, bare post.aat.example for prod, shared ephemeral.post.<zone> for PR envs
Workers, D1, R2, KV, queues, workflows, dispatch NS, logpush, tenant scriptsSUFFIX …-{env} or …_{env}cta-api-dev, db_primary_dev, r2-assets-prod

Declare route hosts as the bare apex once (host: "ca.example.com"). Do not put dev. in the declared host — Tamer prefixes non-prod envs automatically. See Environments → Route expansion.

Resource suffixes and URL prefixes are independent: changing one does not change the other.

Default name patterns ​

When no naming hook or per-resource cloudflareName override is set, Tamer derives Cloudflare names from the logical name + env. The stack identity is available to hooks but is not embedded in the defaults (except worker script names, dispatch namespaces, and shard-group physical names, which include the stack/product).

ResourceDefault name
D1 singledb_{logical}_{env}
D1 shardeddb_{logical}_{YYYYMMDD}_{env}
R2 bucketr2-{logical}-{env}
KV namespacekv_{logical}_{env}
Queueq-{logical}-{env}
Hyperdrivehd-{logical}-{env}
Vectorizevec-{logical}-{env}
AI Gatewayaigw-{logical}-{env}
Pipelinepipe-{logical}-{env}
Workflowwf-{logical}-{env}
Secrets Storesec-{logical}-{env}
Worker script{stack}-{workerKey}-{env} (local omits -{env})
Dispatch namespace{stack}-{logical}-{env} (namespace override → {namespace}-{env}; ephemeral → …-ephemeral; local → bare logical / override)
Tenant dispatch script{service}-{workspace}-{env}
Shard group physical (stack)db_{group}_{NNN}_{stack}_{env} → binding {prefix}_{NNN}
Shard group physical (tenant)db_{group}_{NNN}_{product}_{workspace}_{env} → binding {prefix}_{NNN}
Per-tenant utility D1db_{logicalName}_{product}_{workspace}_{env}

logicalName is stable stack identity — used for state keys and ${tamer:…} references regardless of how the Cloudflare-side name is computed. Cloudflare IDs (UUIDs, etc.) are never in config; they land in state after sync / apply.

Overriding names ​

Three layers, applied in this resolution order:

  1. Per-resource cloudflareName — set on an individual resource config to override just that one resource's name.
  2. Stack naming hooks — naming: { d1Single, d1Shard, r2Bucket, workerName, workflow } — one formula for every resource of a kind.
  3. NamingEngine default — the table above (greenfield).

Stack naming hooks (greenfield + shared patterns) ​

Declare on the root defineConfig({ … }). Each hook receives the stack identity as a parameter so custom formulas can still embed it.

HookUsed forDefault when omitted
d1Singled1 resources with type: "single"db_{logical}_{env}
d1Shardd1 resources with type: "sharded"db_{logical}_{YYYYMMDD}_{env}
r2BucketR2 bucketsr2-{logical}-{env} (date arg is YYYYMMDD at apply time; ignore if unused)
workerNameDeployed Worker script names{stack}-{workerKey}-{env} (or scriptName override)
workflowWorkflow registration nameswf-{logical}-{env} (lowercase)

Not configurable via naming today: KV, queues, hyperdrive, vectorize, pipelines, AI gateway, secrets store, dispatch namespaces, DNS. Those use built-in derived names.

API details (defaults, date parameter, sync matching): JSDoc on NamingConventions from @dragonmastery/tamer (source in src/types.ts).

Per-resource cloudflareName (brownfield) ​

When legacy Cloudflare names differ per logical resource rather than following one formula, set optional cloudflareName on each resource config instead of (or in addition to) stack naming.*:

ts
workflows: [
  {
    logicalName: "referral-event",
    className: "ReferralEventWorkflow",
    cloudflareName: (stack, env) =>
      env === "dev"
        ? "referral-event-workflow-dev"
        : `${stack}-app-saai-referral-event-workflow-${env}`,
  },
],

cloudflareName receives (stack, env, ctx?) where ctx.shardDate is passed for sharded D1. Workers use scriptName (no cloudflareName).

Brownfield adoption ​

Existing Cloudflare resources keep their Cloudflare names — reproduce those patterns via naming hooks and/or per-resource cloudflareName. Then run bootstrap → sync → plan → apply. sync adopts resources whose Cloudflare name equals the derived name.

ts
export default defineConfig({
  stack: "acme",
  compatibility_date: "2025-12-01",
  naming: {
    d1Single: (logical, _stack, env) => `acme-${logical}-${env}`,
    r2Bucket: (logical, _date, _stack, env) => `acme-${logical}-${env}`,
    workerName: (stack, workerKey, env) => `${stack}-${workerKey}-${env}`,
    workflow: (logical, _stack, env) => `acme-${logical}-${env}`,
  },
  workers: { /* … */ },
});

See Brownfield Adoption for the full sync/import flow and tamer import escape hatch.

  • Environments — route hostname prefix expansion
  • Email Service — {env}.post.<zone> prefixed hosts + ephemeral collapse
  • Brownfield Adoption — sync/import flow, escape hatch
  • Config Types — full type reference
  • JSDoc on NamingConventions and CloudflareNameFn from @dragonmastery/tamer

Released under the Tamer Evaluation License.