Skip to content

How extension calls work

How a user Worker, facade, ocd, workerd, and native Provider communicate without ocd proxying business bytes.

An extension call has four participants and two transports. ocd authenticates a session and hands over a socket. After that, workerd talks to the Provider directly.

Participant Role
User Worker Declares bindings.worker + props. Calls facade RPC such as env.FILES.list(). Never sees HOST.
Facade Operator JavaScript loaded from extension.toml [worker].main. Reads this.ctx.props. Calls env.HOST.
workerd The target instance’s supervised pinned runtime. Holds the private HostExtensionFactory, broker fd 4, and the session Cap’n Proto client.
ocd Loads the extension when the instance starts, issues session identity, brokers one socketpair, waits for Provider ACK, then leaves the data path.
Provider Operator native process. Accepts OCP2 + nonce + SCM_RIGHTS on its stdin (fd 0), echoes the nonce in its ACK, and serves HostExtension on the session socket.

There is still one workerd for this instance. The Provider is an instance-owned host child, not a second runtime and not a tenant isolate.

user Worker -- Service Binding RPC + ctx.props --> facade (workerd isolate)
facade -- HOST.call / HOST.stream ----------> session Cap'n Proto
(after the socket exists)
workerd -- OCH1 + session identity / SCM_RIGHTS --> ocd
ocd -- OCP2 + nonce / SCM_RIGHTS + ACK ------> Provider
ocd -- session FD back to workerd ------------> workerd
workerd <--------- session Cap'n Proto -----------> Provider
  1. The user Worker invokes the Service Binding. Admission resolves services[].service as a tagged target: a Worker ID or an extension slug. props stay with the Binding.
  2. The trusted loader instantiates the facade, injects HOST from HostExtensionFactory.get(sessionIdentity), and passes props as ctx.props. globalOutbound is null.
  3. The first HOST use makes workerd send OCH1, the session identity, and one file descriptor on the generation broker (inherited fd 4).
  4. ocd looks up that identity in the private registry (caller Worker, version, and Binding; at most 1,024 sessions). Unknown identity fails closed.
  5. ocd starts the Provider if needed, creates a socketpair, sends OCP2, a fresh 16-byte nonce, and one end of the pair to the Provider control socket, then requires ACK 0 plus the matching nonce.
  6. After ACK, ocd checks the session is still authorized (so a generation or shutdown race cannot deliver a late FD), then returns the other end to workerd.
  7. workerd and the Provider speak Cap’n Proto HostExtension on that socket. Unary call and streamed openStream carry operator-defined method numbers and bytes.

ocd does not proxy, parse, or log the business payload.

Magic Socket Direction Purpose
OCH1 workerd generation broker (fd 4) workerd → ocd Prove a validated session identity and receive a data FD
OCP2 Provider control (stdin, fd 0) ocd → Provider Attach one session FD and nonce; Provider echoes nonce in ACK

Both transfers use SCM_RIGHTS with exactly one FD. Extra FDs, wrong magic, truncated identity, or a missing ACK fail closed.

The registry issues a session identity only for a verified caller Worker, version, and Binding. Different props produce different facade cache keys and sessions. One Provider process may serve many sessions for the same extension name.

Broker EOF or a workerd generation change closes the sessions of that generation. The Provider can be reused by the next generation. Instance shutdown reaps Providers with a bounded TERM/KILL. Crash of the Provider fails in-flight calls; later acquire uses 200 ms–5 s backoff and, after six consecutive failures, stays unavailable until the instance restarts.

Secret or handle User Worker Facade Provider ocd logs
HOST / HostExtensionPort no yes n/a no
Session identity no no no no
Provider path / raw FD no no own stdin / session FD no
Platform tokens, S3, SQLite no no no redacted
ctx.props Binding config only yes only if the facade sends them in the payload no

services[].props → ctx.props and Service Binding RPC follow Cloudflare Context and Service Binding RPC. Local extension resolution, Provider lifecycle, OCH1/OCP2, and HOST are open-compute-only. Do not treat them as hosted Cloudflare capabilities or as members of the Worker API inventory.

Limits, name sharing, and the V7 clean data-directory break are listed in Extension API. A worked example is in Implement an extension.