The root
pithy.config.ts is the concept page for this file, each entry in capabilities is a capability, every name it composes is held to the naming rules, and what it generates into wrangler.jsonc is bindings.
export default definePithyConfig({
name: "acme",
environments: ["staging", "prod"],
domains: { … },
capabilities: [ … ],
});| Key | |
|---|---|
name | The project. Capped at 26 characters — see why |
environments | Every environment this project deploys to. dev can never be declared |
domains | Where each Worker answers, per environment |
capabilities | The composition, in order |
environments is load-bearing
Every declared environment is managed, and there is no second, narrower list.
An environment deployed and not managed is an environment whose secrets have no master key — which is a silent failure with a config field making it look intentional.
So declaring an environment is what costs a manager Worker, a D1, a set of resource names. A project that does not want five declares fewer.
domains is what worker sync reads
A domains block names where a Worker answers per environment, and pithy worker sync writes the route and the BASE_URL it implies.
Order matters, and it is yours
Middleware mounts in the order the array lists, and your app capability composes last.
So your middleware runs after auth has resolved the session and before another capability’s own gate — which is exactly what lets you put an authorization rule over somebody else’s route.
Every capability validates its own block
At assembly, not at first use.
An unknown algorithm, a roster a model cannot rate, a board field that is immutable, a plugin id the kit reserves, a category key outside your taxonomy — all fail on deploy rather than at 3am.
Where each capability’s keys live
| Auth | Auth reference |
| Payments | Payments reference |
| Storage | Storage reference |
| Media | Media reference |
| Email reference | |
| Vector | Vector reference |
| Secrets | Secrets reference |
| Turnstile | Turnstile reference |
| i18n | i18n reference |
| Audit | Audit reference |
| Support | Support reference |
| Testers | Testers reference |
| Ledger | Ledger reference |
| Leaderboard | Leaderboard reference |
| Rating | Rating reference |
| Multiplayer | Multiplayer reference |
| Matchmaking | Matchmaking reference |
| Control plane | Control plane reference |
basePath is on almost all of them
Every capability’s mount point is configurable, defaulting to its own name.
Two consequences worth knowing:
A management client composes its calls from the manifest, which names the resolved path — so a client that hardcoded /payments would 404 against exactly the adopters who customized anything.
Two prefixes are fixed regardless. Email’s tracking callbacks keep /_pithy/email, because a tracking URL is already in somebody’s inbox. And testers’ path is movable but effectively one-shot, for the same reason.
The config is loaded by every CLI command
Which is why a Durable Object class must never be exported from a package entry point — that file is what pithy.config.ts imports, and a cloudflare:workers import on that path takes pithy upgrade down with it.