Naming conventions

Every Cloudflare resource Pithy provisions is named by one rule.

<project>-<env>-<thing>

Kebab-case, project first, environment second. No namespace is exempt.

Why there is a rule at all

The rules and their limits are the reference, pithy provision is what composes a name against them, and one project or two is the decision that sets the first segment.

Every Cloudflare namespace Pithy writes into is flat and account-wide: D1 databases, KV namespaces, R2 buckets, Vectorize indexes, Worker scripts, Workflows, the Secrets Store, the API-token list. None of them can be partitioned.

So the name is the partition. The project segment is the only thing keeping two Pithy projects in one account from adopting or overwriting each other’s resources — because provisioning finds a resource by name and reuses it, and without a project segment find then create means a second project silently inherits the first’s database.

What each namespace gets

NamespaceNameExample
D1<project>-<env>-<binding>acme-prod-db
KV<project>-<env>-<binding>acme-prod-sessions
R2<project>-<env>-<capability>acme-prod-storage
Vectorize<project>-<env>-vector-<index>acme-prod-vector-docs
Worker script<project>-<env>-<capability>acme-prod-email
Workflow<project>-<env>-<capability>-<job>acme-prod-email-send
Secrets Store entry<project>-<env>-<secret>acme-prod-secrets-encryption-keys
API token<project>-<env>-<profile>acme-prod-ci-system

The <thing> segment is the binding name for a resource pithy add proposes into your config, and the capability for a resource that capability’s own provision command creates.

Three things deliberately not on that list

Worker binding names — DB, SESSIONS, MEDIA_BUCKET — never carry the project. A binding is a variable name inside your own Worker, and two projects cannot collide on it. Prefixing one would be a breaking change to your code for no benefit.

Secret registry keys never carry it either. For a Secrets Store secret, the registry key is the Worker binding name; only the store entry it resolves to is scoped.

Table names stay pithy_<capability>_<table>. They live inside one database, which is already project-scoped by the database’s own name.

Two stores a name cannot reach

Cloudflare Images and Stream are the exception. An asset in either is keyed by an id Cloudflare mints on upload rather than by a name you choose, so there is nothing to put a project segment into.

Ownership is carried in metadata instead — every asset Pithy creates is stamped with the project and the environment, the same two keys in both stores, so one query answers what does this project own across them. The stamp is merged last, so a caller’s own metadata cannot displace it, and a Worker that cannot name its project refuses to mint rather than writing an asset nobody can attribute.

That is weaker than a name, deliberately: nothing stops two projects’ assets sitting side by side, because ids are unique and neither can adopt the other’s. What the stamp buys is attribution.

Why project first, environment second

Project first, because the project is the ownership boundary and every operation that asks is this mine keys on the leading segment. Teardown recomputes a name rather than scanning for it. Token listing filters on the prefix. A hashed or truncated project segment would break both, which is why the project is never truncated.

Environment second, because these names land in listings nobody can filter — wrangler d1 list, the account dashboard, the Secrets Store. All any of them offer is sort order.

Sorting should group a project’s resources and then that project’s environments, so everything belonging to production sits in one block. Putting the environment last scatters production through the listing, interleaved with staging. That is exactly the moment somebody acts on the wrong one.

prod, not production

The environment sits in the middle of every name and is never truncated, so every character of environment costs a character of project name, one for one.

production is six characters longer than prod — six characters off the project-name cap for every adopter, spent on a word that says nothing prod does not. --env production is answered with an error naming prod.

An environment stops at seven characters, the length of staging. That is a derivation input rather than a preference: every project-name budget is computed against a seven-character environment, and a longer one would retroactively shrink a cap projects have already been accepted under. A provisioned project cannot be renamed, so the cap cannot move.

A custom environment is fine — live, eu-prod — held to the same character set and the same seven. It is read verbatim, never kebab-cased, unlike the project name: a project name is prose typed once, so Acme Corp is politely composed into acme-corp, while an environment is an identifier repeated in a flag, a filename and a wrangler key.

global in the environment slot

Some things are shared across every environment on purpose. They put the literal global in the slot rather than omitting it, so the scheme has no exception to remember.

ResourceWhy it is global
The email suppression listDo not email this person again is not an environment-local fact. An address that hard-bounced in production must not be retried from staging, or the sending domain’s reputation pays for the distinction
The secrets manager’s own credentialOne per project
The support inbox’s bucketA thread does not belong to an environment; environments are separated by which bucket an operator binds
Email Routing rulesOne rule per zone; environments are separated by which Worker it points at

global is a scope decision rather than a shortcut. Anything not on a list like that one is per-environment.

The project name

It comes from name in the root pithy.config.ts, and nothing else. It is never guessed.

A resolver that guesses exists for display, falling back to a Worker’s name and then the directory basename — good enough to print, catastrophic to provision under, because teardown would later compute names matching nothing, delete nothing, and exit 0.

It starts with a letter, holds only lowercase letters, digits and single hyphens, and stops at 26 characters. Charset and length are one rule with one home because they fail the same way. Acme Corp is fine; 2026-launch is not.

The rule is enforced where the name is minted and again every time it is read, so a name that cannot become a Worker script name is refused before anything is provisioned — rather than at the first host deploy, which would leave a project half-built under names it can never finish using.

The caps differ a lot by namespace

Cloudflare’s limits are not one number, and Pithy holds each one separately.

NamespaceCapOver it
Worker script63Refused
Workflow64Refused
Vectorize index64Refused
R2 bucket63Truncated
KV namespace title512Truncated
D1 database128 (Pithy’s)Truncated
Secrets Store entry128 (Pithy’s)Truncated
API token128 (Pithy’s)Truncated

The Worker script cap is 63 rather than Cloudflare’s general 255 once workers.dev is on, which it is by default. Three of those caps are Pithy’s own where Cloudflare documents none — a stated ceiling is better than discovering an undocumented one in production.

Naming rules and limits has the arithmetic, including how much room a 12-character project leaves for the rest of the name.

ESC