Addons
district addon creates and removes a workspace's addons: D1 databases, R2 buckets and KV namespaces. An addon belongs to a workspace, not to an app. Every member of the workspace can read it; a member with the resource permissions can create, rename and delete it. No clone is read: each subcommand accepts --workspace <id> and the environment flags, and a caller who can open several workspaces names one with --workspace <id>.
A workspace holds at most 10 D1 databases, 10 R2 buckets and 3 KV namespaces, deleting and failed ones included. A display name is 1 to 100 characters and unique among the live addons of one kind in the workspace, so the same name can name a D1 database and an R2 bucket.
Creating an addon binds it to no app. An app binds it through its own wrangler configuration, which names the addon by its alias, and the next deployment reads that configuration. Bindings and environments gives the rules. The alias is the hadd_ id in lower case with the underscore replaced by a hyphen: hadd-01k4x0j8w1qz5b7n3g2r6tq9yd. It never changes; a rename changes the display name only.
District ships no SQL or migration command. A D1 schema is the app's own responsibility, applied through app code at runtime. One app is the schema owner of each database: two apps that migrate the same database can apply conflicting changes, and a rollback of a deployment does not undo a schema change.
The CLI and the API show no Cloudflare identifier of an addon. Deleting an addon destroys its data. The deletion cannot be undone, and District promises no recovery for R2 objects or KV values. Deleting an app deletes no addon. district addon delete requires --confirm.
A subcommand that names one addon takes a hadd_ id or a display name. An id needs no workspace; a value that starts with hadd_ and is not a well-formed id is a usage error, not a name. A name is looked up in the selected workspace, in every kind unless --kind names one. A name that two kinds hold is a usage error that lists the candidates with their --kind.
In a terminal, each status carries a symbol and a color: ✔ ready, ✖ failed, ○ provisioning. A status has one of these values:
provisioning: the addon exists and the platform is creating the resource. The platform retries a refused create on its own schedule.ready: the resource exists. An app can bind the addon.failed: the provider refused the resource, or the platform gave up. The recorded error says why. The addon holds a workspace slot until it is deleted.deleting: the deletion is claimed and the platform is removing the resource. The addon leaves lists once the provider confirms.deleted: the resource is gone. No list or lookup answers the addon.
Each subcommand exits 3 when the addon service does not answer. Run the command again after the number of seconds in the message. The message says what the call left behind.
district addon create
district addon create <kind> <name> [--workspace <id>] [--json] [--production | --staging | --dev]Creates a D1 database (d1), an R2 bucket (r2) or a KV namespace (kv) in the workspace, and waits for the resource. The command prints the addon as soon as it exists, then reads it every 2 seconds for up to one minute. Once the addon is ready, it prints the wrangler configuration fragment that binds it.
Creating an addon binds it to no app. An app binds the addon through its own wrangler configuration, which names the addon by its alias, and the next deployment reads that configuration. Each environment section of the configuration lists its own bindings.
Arguments
| Argument | Description |
|---|---|
<kind> | The resource to create: d1, r2 or kv. |
<name> | The display name, 1 to 100 characters. It must be free among the live addons of the kind in the workspace. |
Flags
| Flag | Description | Default |
|---|---|---|
--workspace <id> | The workspace, when you can open more than one. The error lists them. | - |
--json | Prints the addon after the wait as one JSON object, without the snippet or any progress line. | false |
--production | --staging | --dev | The environment to act in. Pass one at most. Without a flag, the command uses the environment that the last district login recorded, or production. | - |
Output
$ district addon create d1 orders
created orders (hadd_01K4X0J8W1QZ5B7N3G2R6TQ9YD)
kind d1
status ○ provisioning
workspace ws_01K4X0J8W1QZ5B7N3G2R6TQ9YD
alias hadd-01k4x0j8w1qz5b7n3g2r6tq9yd
status ✔ ready
Creating an addon binds it to no app. Add the binding to the wrangler configuration of the app that uses it; the
next deployment reads it. Each environment section lists its own bindings.
hadd-01k4x0j8w1qz5b7n3g2r6tq9yd
{
"d1_databases": [
{
"binding": "DB",
"database_name": "hadd-01k4x0j8w1qz5b7n3g2r6tq9yd"
}
]
}The snippet puts the alias in the field the provider resolves a name by: database_name under d1_databases for D1, bucket_name under r2_buckets for R2 and id under kv_namespaces for KV. The binding name (DB, FILES, CACHE) is a placeholder the app renames.
A provider that answers within the request makes the addon ready at once, and the command prints the snippet without a wait. An addon whose provisioning fails prints its recorded error and exits 1; the addon holds a workspace slot until it is deleted. An addon still provisioning after one minute exits 1 with district addon show <id> in the message: the platform keeps trying on its own schedule.
The command sends one creation key per invocation and repeats a request that the API answered 503 or did not answer with the same key, so a repeat cannot create a second addon. After three attempts it exits 3 and says the addon may exist. A workspace at its cap, a taken name, and a workspace the caller cannot create resources in are refused with the API's own message.
Exit codes
| Exit code | Meaning |
|---|---|
| 0 | The addon is ready. Its snippet is printed. |
| 1 | The API refused the call with its own message: the workspace is at its cap, the name is taken, or the caller cannot create resources in the workspace. Provisioning failed, the addon was still provisioning after one minute, or no read of it answered in that minute. |
| 2 | The command line names a kind that is not d1, r2 or kv, or the caller can open several workspaces and names none. |
| 3 | The addon service did not answer. The message says what the call left behind. |
district addon list
district addon list [--kind d1|r2|kv] [--workspace <id>] [--json] [--production | --staging | --dev]Prints a header row, then one row per addon of the workspace, newest first: the name, the kind, the status and the id. Deleted addons are omitted; a deleting addon is listed until the provider confirms. A workspace with no addon prints nothing and exits 0.
Flags
| Flag | Description | Default |
|---|---|---|
--kind d1|r2|kv | Lists addons of one kind only. | every kind |
--workspace <id> | The workspace, when you can open more than one. The error lists them. | - |
--json | Prints the addons as one JSON array. | false |
--production | --staging | --dev | The environment to act in. Pass one at most. Without a flag, the command uses the environment that the last district login recorded, or production. | - |
Output
$ district addon list
NAME KIND STATUS ADDON
orders d1 ✔ ready hadd_01K4X0J8W1QZ5B7N3G2R6TQ9YD
uploads r2 ✖ failed hadd_01K4X0J8W1QZ5B7N3G2R6TQ9YEExit codes
| Exit code | Meaning |
|---|---|
| 0 | The rows were printed. A workspace with no addon also exits 0. |
| 1 | The API refused the call with its own message. |
| 2 | The command line names a kind that is not d1, r2 or kv, or the caller can open several workspaces and names none. |
| 3 | The addon service did not answer. The message says what the call left behind. |
district addon show
district addon show <addon> [--kind d1|r2|kv] [--workspace <id>] [--json] [--production | --staging | --dev]Prints the addon's state, its alias, its recorded error while the API holds one, and the apps that bind it.
The apps list is a display list: the API answers up to 100 apps the caller can read and states no total. A list at the bound gets a sentence that says more can bind the addon. The command reads no deployment list; district addon delete reports the complete reference set when it refuses. An app is in the list while one of its retained deployments binds the addon, including a deployment that no longer serves.
Arguments
| Argument | Description |
|---|---|
<addon> | The addon, by hadd_ id or by display name. An id needs no workspace. A name is looked up in the selected workspace, in every kind unless --kind names one. |
Flags
| Flag | Description | Default |
|---|---|---|
--kind d1|r2|kv | The kind of the addon a display name selects. Required when two kinds hold the name; the error lists them. | - |
--workspace <id> | The workspace, when you can open more than one. The error lists them. | - |
--json | Prints the addon, with boundApps, as one JSON object. | false |
--production | --staging | --dev | The environment to act in. Pass one at most. Without a flag, the command uses the environment that the last district login recorded, or production. | - |
Output
$ district addon show orders
addon orders (hadd_01K4X0J8W1QZ5B7N3G2R6TQ9YD)
kind d1
status ✔ ready
alias hadd-01k4x0j8w1qz5b7n3g2r6tq9yd
created 2026-08-25T09:00:00Z
updated 2026-08-25T09:00:05Z
no app binds this addonExit codes
| Exit code | Meaning |
|---|---|
| 0 | The addon was printed. |
| 1 | The API refused the call with its own message. |
| 2 | The command line names a malformed id, an addon nobody can read, a name no kind holds, a name two kinds hold without --kind, or the caller can open several workspaces and names none. |
| 3 | The addon service did not answer. The message says what the call left behind. |
district addon rename
district addon rename <addon> <new-name> [--kind d1|r2|kv] [--workspace <id>] [--json] [--production | --staging | --dev]Changes the display name and prints the new name with the unchanged id and alias. No app configuration changes. A taken name, a name outside its bounds, and a deleting addon are refused with the API's own message.
Arguments
| Argument | Description |
|---|---|
<addon> | The addon, by hadd_ id or by display name. An id needs no workspace. A name is looked up in the selected workspace, in every kind unless --kind names one. |
<new-name> | The new display name, 1 to 100 characters. It must be free among the live addons of the kind in the workspace. |
Flags
| Flag | Description | Default |
|---|---|---|
--kind d1|r2|kv | The kind of the addon a display name selects. Required when two kinds hold the name; the error lists them. | - |
--workspace <id> | The workspace, when you can open more than one. The error lists them. | - |
--json | Prints the addon after the rename as one JSON object. | false |
--production | --staging | --dev | The environment to act in. Pass one at most. Without a flag, the command uses the environment that the last district login recorded, or production. | - |
Output
$ district addon rename orders orders-v2
renamed orders to orders-v2 (hadd_01K4X0J8W1QZ5B7N3G2R6TQ9YD)
alias hadd-01k4x0j8w1qz5b7n3g2r6tq9yd (unchanged)Exit codes
| Exit code | Meaning |
|---|---|
| 0 | The addon has its new name. |
| 1 | The API refused the call with its own message: the name is taken or outside its bounds, the addon is deleting, or the caller cannot update resources in the workspace. |
| 2 | The command line names a malformed id, an addon nobody can read, a name no kind holds, a name two kinds hold without --kind, or the caller can open several workspaces and names none. |
| 3 | The addon service did not answer. The message says what the call left behind. |
district addon delete
district addon delete <addon> --confirm [--kind d1|r2|kv] [--workspace <id>] [--json] [--production | --staging | --dev]Deletes the addon and destroys its data. The deletion cannot be undone. --confirm is required: without it the command exits 2 before any call and deletes nothing. No terminal question stands in for the flag, so a script and a person state the same intent.
The deletion is claimed at once: no app can bind the addon from then on. deleted means the provider confirmed the resource is gone within the request. deleting means the platform is still removing it; the addon leaves district addon list once the provider confirms.
Arguments
| Argument | Description |
|---|---|
<addon> | The addon, by hadd_ id or by display name. An id needs no workspace. A name is looked up in the selected workspace, in every kind unless --kind names one. |
Flags
| Flag | Description | Default |
|---|---|---|
--confirm | Confirms that the data is destroyed. Without it, the command deletes nothing and exits 2. | false |
--kind d1|r2|kv | The kind of the addon a display name selects. Required when two kinds hold the name; the error lists them. | - |
--workspace <id> | The workspace, when you can open more than one. The error lists them. | - |
--json | Prints the deleted id and its status as one JSON object. | false |
--production | --staging | --dev | The environment to act in. Pass one at most. Without a flag, the command uses the environment that the last district login recorded, or production. | - |
Output
$ district addon delete orders-v2 --confirm
deleted orders-v2 (hadd_01K4X0J8W1QZ5B7N3G2R6TQ9YD): the data is destroyedThe API refuses an addon that a retained deployment binds. The message is followed by a table of the references the caller can see, up to 100, the count of references that belong to apps the caller cannot see, and a sentence when the table was cut. A deployment is retained until District removes its script, 14 days or more after the deployment stops serving. Remove the binding from the app's configuration, deploy to each target that bound it, and repeat after the earlier deployments are removed.
The API refuses an R2 bucket that still holds objects. The addon is ready again, and the message states the object count, with at least when the scan stopped at its bound. Bind the bucket to an app that empties it, then repeat. A count of 0 with a refusal can be an unfinished multipart upload, which the count does not see; abort it through the app.
Exit codes
| Exit code | Meaning |
|---|---|
| 0 | The deletion is claimed. deleted means the data is destroyed. deleting means the platform is still removing the resource and its data. |
| 1 | The API refused the call with its own message: a deployment binds the addon, the bucket holds objects, the provider refused, or the caller cannot delete resources in the workspace. |
| 2 | The command line lacks --confirm, names a malformed id, an addon nobody can read, a name no kind holds, a name two kinds hold without --kind, or the caller can open several workspaces and names none. |
| 3 | The addon service did not answer. The message says what the call left behind. |