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}.
| Surface | Env placement | Examples |
|---|---|---|
HTTP hostnames (tamerRoutes) | PREFIX {env}.{apex} | dev.ca.example.com, pr-1234.api.example.com |
| Prod HTTP hostnames | Bare apex (no env label) | ca.example.com |
| DNS record names | Operator-controlled (usually prefix via ${tamer:env}.…) | *.${tamer:env}.dev.todo.com → *.pr-7.dev.todo.com |
| Email routing/sending hosts | PREFIX {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 scripts | SUFFIX …-{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).
| Resource | Default name |
|---|---|
| D1 single | db_{logical}_{env} |
| D1 sharded | db_{logical}_{YYYYMMDD}_{env} |
| R2 bucket | r2-{logical}-{env} |
| KV namespace | kv_{logical}_{env} |
| Queue | q-{logical}-{env} |
| Hyperdrive | hd-{logical}-{env} |
| Vectorize | vec-{logical}-{env} |
| AI Gateway | aigw-{logical}-{env} |
| Pipeline | pipe-{logical}-{env} |
| Workflow | wf-{logical}-{env} |
| Secrets Store | sec-{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 D1 | db_{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:
- Per-resource
cloudflareName— set on an individual resource config to override just that one resource's name. - Stack
naminghooks —naming: { d1Single, d1Shard, r2Bucket, workerName, workflow }— one formula for every resource of a kind. NamingEnginedefault — 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.
| Hook | Used for | Default when omitted |
|---|---|---|
d1Single | d1 resources with type: "single" | db_{logical}_{env} |
d1Shard | d1 resources with type: "sharded" | db_{logical}_{YYYYMMDD}_{env} |
r2Bucket | R2 buckets | r2-{logical}-{env} (date arg is YYYYMMDD at apply time; ignore if unused) |
workerName | Deployed Worker script names | {stack}-{workerKey}-{env} (or scriptName override) |
workflow | Workflow registration names | wf-{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.*:
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.
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.
Related docs
- 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
NamingConventionsandCloudflareNameFnfrom@dragonmastery/tamer