Present and denying by default
A Worker that composes the capability and has **never been **connected answers every control-plane route with controlplane/not_connected.
There is no flag to leave off, no backdoor, and nothing enabled silently. Connecting is a deliberate act, and until you perform it the seam’s only behavior is refusal.
Your Worker is the authority
The management client is a client. It holds a private key; you hold the public one, you decide what it may do, and you can revoke it without asking anyone.
Adding your own admin route
app.post(
`${base}/entitlements/grant`,
requireControlPlane(PAYMENTS_ENTITLEMENT_GRANT_SCOPE),
zValidator("json", EntitlementGrantRequest, validationHook),
(c) => grantEntitlement(c, c.req.valid("json")),
);A capability declares admin routes behind a scope guard and they compose into the tree with nothing to wire — the same federation as migrations, error codes and audit actions.
Declare them via adminRoutes as well — the reference has the shape — so a management client learns how to call them from your Worker rather than from a route table it ships with.
Two mistakes that break a route permanently
Guards before validators, always. A validator ahead of the guard turns a 401 into a 400 and tells an unauthenticated caller which requests were well-formed. On this seam that is a live oracle.
Two things are called “control plane”, and they are not related
Cloudflare’s control plane is outbound. The REST API the CLI calls to create a database, manage a namespace, or deploy a Worker. It authenticates with your Cloudflare token, runs from the CLI or CI, and never appears in a request handler. Its failures carry cloudflare/* codes.
This seam is inbound. How a management client calls your Worker to administer your data. It authenticates with a key you registered, runs inside a request handler, and never talks to Cloudflare at all. Its failures carry controlplane/* codes.
They share a name and nothing else.
Every call is audited
Under the control-plane actor kind, carrying the connection id and the dashboard user’s own subject.
Your trail can then answer what did the management client do separately from what did my users do — which is the question you will actually ask.
Denials are audited too. This is the surface where an unaudited blocked attempt is least acceptable. An audit write that fails never turns a correct denial into a 500 — the denial has already been decided by the time it is recorded.
The seam is MIT and never gated
It lives in core, and restricting it would make you can build your own client* against your own Worker* untrue.
Admin routes contributed by a capability inherit that capability’s license.