# What Is Cap'n Web RPC and How Cloudflare OS Uses It for Secure Edge Communication

> Discover Capn Web RPC, Cloudflare's protocol for secure, type-safe communication between browser sandboxes and edge Durable Objects. Learn its role in Cloudflare OS.

- Repository: [Cloudflare/cloudflare-os](https://github.com/cloudflare/cloudflare-os)
- Tags: deep-dive
- Published: 2026-09-04

---

**Cap'n Web RPC is Cloudflare's lightweight, bidirectional object-capability protocol that enables type-safe remote procedure calls over WebSockets between browser sandboxes and edge Durable Objects.**

The `cloudflare/cloudflare-os` repository (also known as the Gadgets Workshop platform) uses Cap'n Web as its default RPC mechanism to connect frontend agents, backend gatekeepers, and sandboxed gadget code. This protocol extends the Cap'n Proto design philosophy to run efficiently over WebSocket connections or Cloudflare Workers' built-in RPC channels, operating seamlessly in both browser iframes and edge environments.

## Core Architecture of Cap'n Web RPC

Cap'n Web implements an **object-capability** model where remote objects appear as local JavaScript stubs. The protocol serializes method calls into messages that traverse WebSocket connections, allowing bidirectional communication between untrusted browser code and privileged server-side resources.

### Bidirectional Stubs and Object Capabilities

The fundamental abstraction in Cap'n Web is the **`RpcTarget`**. When a client connects, `RpcSession` exports a target object that appears as a normal JavaScript object on the remote side. Method invocations on these stubs automatically serialize into RPC messages, while the server returns stubs or values that can be invoked immediately.

According to the AGENTS documentation, the protocol establishes persistent WebSocket connections between the frontend (`packages/workshop-frontend`) and backend (`packages/workshop-backend`). The `RpcSession` wraps the WebSocket transport and manages the lifecycle of these remote references.

### Promise Pipelining for Zero-Round-Trip Calls

Cap'n Web eliminates network latency through **promise pipelining**. When an RPC method returns a stub wrapped in a Promise, you do not need to `await` the resolution before calling methods on the result. The protocol allows you to invoke methods directly on the Promise object; the server resolves the underlying stub before executing the pipelined arguments.

As implemented in the agent bridge code ([`packages/workshop-backend/src/agent.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/agent.ts)), this optimization removes an extra round-trip between the browser iframe and the edge Durable Object. The server-side resolution happens transparently before the arguments are delivered to the final target.

### Security Boundaries and Prototype Protection

Security is enforced at the serialization layer. Cap'n Web automatically strips prototype-shadowing keys (such as `__proto__` and `toJSON`) from objects traveling over the wire, ensuring JSON-compatible payloads cannot manipulate receiver prototypes.

The browser-side implementation runs inside a sandboxed iframe that Cloudflare injects via `postMessage()`. This iframe has no direct network access; all communication flows through the injected RPC channel, creating a strict capability boundary between untrusted gadget code and the host system.

## How Cloudflare OS Implements Cap'n Web RPC

Cloudflare OS uses Cap'n Web as the universal glue connecting UI components, agent processes, and gatekeeper services.

### Frontend-to-Backend Communication

The single-page application in `packages/workshop-frontend` establishes a persistent WebSocket connection to the Workshop kernel. The connection initialization pattern appears in [`packages/workshop-frontend/src/rateLimitedCapability.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-frontend/src/rateLimitedCapability.ts), where the frontend constructs an `RpcSession` over the WebSocket:

```typescript
import { RpcSession, RpcTarget } from "capnweb";

const ws = new WebSocket(`${location.origin}/api`);
const session = new RpcSession(ws);

export const workshopApi: RpcTarget = session.exportTarget({
  // Methods defined in packages/workshop-shared/src/api.ts
});

```

This exported stub exposes kernel methods (such as `getUserProfile` and `submitChange`) to sandboxed contexts while maintaining type safety through shared TypeScript interfaces.

### Agent-to-Gadget Bridge

When users open gadgets, the browser loads sandboxed iframes that receive a `gadget` stub pointing to a Durable Object on the edge. In [`packages/workshop-backend/src/agent.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/agent.ts) (lines 700-717), the backend injects this `RpcTarget` into the iframe context. Calls on the stub automatically route over Cap'n Web, allowing gadget code to invoke server-side methods as if they were local functions.

The iframe environment cannot access the network directly; all database queries, storage operations, and external API calls must flow through this capability channel.

### Type-Safe API Definitions

The shared package ([`packages/workshop-shared/src/api.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/api.ts)) defines the canonical RPC interfaces imported by both client and server. This guarantees that the wire format matches TypeScript signatures across the frontend, backend, and gatekeeper workers (Context, Scheduler, and Cloudflare-Observability services).

## Implementation Examples

### Creating a Cap'n Web Session

Establishing communication requires wrapping a WebSocket transport with `RpcSession` and exporting capability targets:

```typescript
import { RpcSession, RpcTarget } from "capnweb";

// Persistent WebSocket to the backend router
const ws = new WebSocket(`${location.origin}/api`);
const session = new RpcSession(ws);

// Export methods that gadgets can invoke
export const api: RpcTarget = session.exportTarget({
  async getUserProfile() { /* ... */ },
  async submitChange(data) { /* ... */ }
});

```

This pattern mirrors the production implementation in [`packages/workshop-frontend/src/rateLimitedCapability.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-frontend/src/rateLimitedCapability.ts) (lines 7-13).

### Leveraging Promise Pipelining

Gadget code can chain remote calls without awaiting intermediate stubs:

```typescript
// Inside the sandboxed iframe
const gadget: RpcTarget = parent.gadget; // Injected by backend

// getSession returns Promise<RpcTarget>; pipeline the next call
const sessionPromise = gadget.getSession();
const docs = await sessionPromise.listDocuments(); // Zero extra round-trip

```

The server resolves `getSession` and executes `listDocuments` in a single network hop, as documented in AGENTS.md (lines 98-100).

### Explicit Resource Disposal

While Cap'n Web automatically disposes stub arguments when RPC calls complete, long-lived objects (such as those stored in React state) require manual cleanup:

```typescript
async function queryDatabase() {
  const conn = await gadget.connectDatabase(); // Returns stub
  await conn.query("SELECT * FROM notes");
  
  // Free edge resources immediately
  conn[Symbol.dispose]();
}

```

The [`GadgetUI.tsx`](https://github.com/cloudflare/cloudflare-os/blob/main/GadgetUI.tsx) component in the frontend demonstrates patterns for wrapping stubs before using them with `useState` to ensure proper disposal timing.

## Key Source Files in cloudflare/cloudflare-os

| File | Purpose |
|------|---------|
| [`AGENTS.md`](https://github.com/cloudflare/cloudflare-os/blob/main/AGENTS.md) | Protocol overview, promise pipelining rules, and stub disposal guidelines |
| [`packages/workshop-shared/src/api.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/api.ts) | Canonical TypeScript interfaces for type-safe RPC |
| [`packages/workshop-shared/node_modules/capnweb/README.md`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/node_modules/capnweb/README.md) | Cap'n Web API documentation and serialization rules |
| [`packages/workshop-frontend/src/rateLimitedCapability.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-frontend/src/rateLimitedCapability.ts) | Frontend WebSocket session initialization |
| [`packages/workshop-frontend/src/GadgetUI.tsx`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-frontend/src/GadgetUI.tsx) | React integration patterns for RPC stubs |
| [`packages/workshop-backend/src/agent.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/agent.ts) | Agent-side stub injection and iframe communication |
| [`packages/workshop-backend/src/overseer.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/overseer.ts) | Kernel session management for gatekeepers |

## Summary

- **Cap'n Web RPC** provides object-capability remote procedure calls over WebSockets, enabling secure communication between browser sandboxes and Cloudflare edge infrastructure.
- **Promise pipelining** eliminates network round-trips by allowing method calls on unresolved stubs, minimizing latency in gadget interactions.
- **Automatic prototype stripping** and iframe sandboxing create defense-in-depth security for untrusted code execution.
- **Shared TypeScript definitions** in [`packages/workshop-shared/src/api.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/api.ts) ensure type safety across the entire Cloudflare OS stack.
- **Explicit disposal** via `Symbol.dispose()` prevents resource leaks in long-lived Durable Object stubs.

## Frequently Asked Questions

### How does Cap'n Web RPC differ from standard HTTP REST APIs?

Cap'n Web RPC uses persistent WebSocket connections and object-capability semantics rather than stateless request-response cycles. Methods appear as local JavaScript objects (stubs) rather than requiring manual HTTP client construction, and promise pipelining allows chaining dependent calls without waiting for intermediate network round-trips.

### Can Cap'n Web RPC work outside of Cloudflare Workers?

While optimized for Cloudflare's edge runtime, the protocol runs over standard WebSockets and can operate in any environment supporting the `capnweb` library. However, the specific security model (sandboxed iframes with `postMessage` injection) relies on Cloudflare OS's architecture.

### What happens if I don't dispose of RPC stubs manually?

Cap'n Web automatically disposes stubs when the RPC call that created them completes. However, storing stubs in React state or global variables creates long-lived references that persist beyond single calls. Without manual `Symbol.dispose()`, these stubs consume memory and capability slots on the edge Durable Object until garbage collection occurs.

### Where is the Cap'n Web protocol specification defined?

The canonical documentation resides in [`packages/workshop-shared/node_modules/capnweb/README.md`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/node_modules/capnweb/README.md) within the repository. Additionally, [`AGENTS.md`](https://github.com/cloudflare/cloudflare-os/blob/main/AGENTS.md) documents Cloudflare OS-specific conventions for promise pipelining, stub disposal, and security boundaries that extend the base protocol.