# How Cloudflare OS Enables Real-Time Multiplayer for Gadgets Using Durable Objects

> Discover how Cloudflare OS enables real-time multiplayer for Gadgets. Learn how Durable Objects and Cap'n Proto RPC create strongly consistent state and push updates for seamless gaming experiences.

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

---

**Cloudflare OS powers real-time multiplayer in Gadgets by combining Durable Objects for strongly consistent state with Cap'n Web RPC for bidirectional communication, allowing servers to push updates to clients through function stubs.**

Cloudflare OS provides the infrastructure for building collaborative web applications through its Gadget framework. By leveraging Cloudflare's stateful serverless primitives and modern RPC patterns, developers can implement Cloudflare OS real-time multiplayer functionality without managing WebSocket connections or consensus algorithms. The architecture centers on Durable Objects that maintain shared game state and Cap'n Web RPC that enables servers to invoke client methods asynchronously.

## Architecture Overview: Durable Objects and Cap'n Web RPC

### Stateful Serverless Primitives

Every Gadget in Cloudflare OS runs on top of a **Durable Object**, which provides a single instance that automatically synchronizes across all connected clients. According to the source code in [`packages/workshop-backend/src/agent.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/agent.ts), this guarantees that only one instance processes all RPC calls, eliminating race conditions and ensuring the shared state remains consistent.

### Bidirectional RPC Communication

The platform exposes Gadget APIs through **Cap'n Web RPC**, allowing clients to call server methods while also passing **function stubs** that the server can later invoke. This creates a publish/subscribe pattern where the server can push updates to clients without polling. The implementation relies on `RpcTarget` subclasses that act as callbacks for state changes.

## Server-Side Implementation: Managing Client Subscriptions

The server maintains a registry of connected clients using persistent references to RPC stubs. As documented in [`packages/workshop-backend/src/agent.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/agent.ts) (lines 19-23), the Durable Object stores a set of subscribed client callbacks and invokes each one when the shared state changes.

When a client connects, it registers a callback stub through the `subscribe()` method. The server uses `dup()` to create a long-lived reference and `onRpcBroken()` to handle disconnections:

```typescript
// packages/workshop-backend/src/gadget.ts
import { RpcTarget } from "cloudflare:workers";

export class Gadget extends DurableObject {
  // Persistent set of client callbacks
  private subscribers = new Set<RpcTarget>();

  // Called by a client to register its callback stub
  async subscribe(callback: RpcTarget) {
    const cb = callback.dup();           // keep a long-lived reference
    this.subscribers.add(cb);
    // Remove the stub if the client disconnects
    cb.onRpcBroken(() => this.subscribers.delete(cb));
  }

  // Example game move from a client
  async makeMove(playerId: string, move: any) {
    // Update durable-object storage with the new move
    const newState = await this.loadState();

    // Broadcast the updated state to all clients
    for (const cb of this.subscribers) {
      // `update` is a method defined on the client stub
      await cb.update(newState);
    }
  }
}

```

## Client-Side Implementation: RpcTarget Stubs

Clients implement real-time updates by creating an **RpcTarget** subclass that defines methods the server can invoke. The pattern shown in [`packages/workshop-backend/src/agent.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/agent.ts) (lines 33-47) demonstrates how clients register these stubs using `gadget.subscribe()`.

Because the stub is an RPC reference, the server can call it asynchronously, and the client's UI updates in real time. The client runs inside a sandboxed iframe where the framework automatically forwards RPC calls over `postMessage`:

```typescript
// packages/workshop-frontend/src/client.ts
import { RpcTarget } from "cloudflare:workers";

class Callback extends RpcTarget {
  // Called by the server whenever the shared state changes
  update(state) {
    // Refresh UI – e.g. re-render a game board
    renderGame(state);
  }

  // When the iframe unloads, discard the stub so the server can clean up
  [Symbol.dispose]() {
    gadget.subscribe(this); // re-subscribe on reconnect if needed
  }
}

// `gadget` is the top-level RPC stub automatically injected by the platform
gadget.subscribe(new Callback());

// To make a move:
async function placePiece(row: number, col: number) {
  await gadget.makeMove("player-1", { row, col });
}

```

## State Persistence and Consistency Guarantees

Durable Objects guarantee strong consistency, but developers must persist state to storage rather than keeping it only in memory. As noted in [`packages/workshop-backend/src/agent.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/agent.ts) (lines 56-58), all state must be stored in the Durable Object's KV/SQLite storage to survive restarts.

Always use the storage API to ensure data survives migrations or unexpected terminations:

```typescript
// Inside the DurableObject class
async loadState() {
  const raw = await this.storage.get("gameState");
  return raw ?? { board: [], turn: "player-1" };
}

async saveState(state) {
  await this.storage.put("gameState", state);
}

```

## Real-Time Collaboration Without WebSockets

Cloudflare OS abstracts away WebSocket management entirely. According to the repository's [`README.md`](https://github.com/cloudflare/cloudflare-os/blob/main/README.md) (lines 44-48), the platform explicitly supports real-time multiplayer experiences similar to Google Docs or collaborative whiteboards by handling all networking through the Durable Object and Cap'n Web RPC layer. Developers never need to manage connection lifecycles or message broadcasting logic manually.

## Summary

- **Cloudflare OS real-time multiplayer** relies on Durable Objects for strongly consistent, stateful serverless compute.
- The **Cap'n Web RPC** protocol enables bidirectional communication where servers push updates to clients via function stubs.
- Clients implement **RpcTarget** subclasses that servers invoke when shared state changes.
- The **subscribe()** pattern in [`packages/workshop-backend/src/agent.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/agent.ts) manages client registrations with automatic cleanup via `onRpcBroken()`.
- All game state must persist to **Durable Object storage** to survive restarts and ensure consistency across sessions.

## Frequently Asked Questions

### How does Cloudflare OS handle client disconnections in multiplayer Gadgets?

When a client disconnects, the Durable Object automatically detects the broken RPC connection through the `onRpcBroken()` callback. As implemented in [`packages/workshop-backend/src/agent.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/agent.ts), the server removes the client's stub from the subscription set, preventing failed update attempts to disconnected clients without requiring explicit unsubscribe calls from the client.

### What makes Durable Objects suitable for real-time multiplayer games?

Durable Objects provide a single, strongly consistent instance that processes all requests for a specific game session. This eliminates race conditions because only one instance updates the shared state at any time. According to the Cloudflare OS source code, this guarantee extends across all connected clients, making it ideal for turn-based or real-time collaborative applications.

### Do developers need to implement WebSocket handling for Gadget multiplayer?

No. Cloudflare OS handles all underlying communication through **Cap'n Web RPC**, which automatically forwards calls over `postMessage` within the sandboxed iframe environment. The framework manages connection lifecycles, reconnection logic, and state synchronization, allowing developers to focus on game logic rather than networking code.

### How is game state persisted in Cloudflare OS multiplayer Gadgets?

Game state must be explicitly written to the Durable Object's storage using `this.storage.put()` rather than kept in memory. As documented in [`packages/workshop-backend/src/agent.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/agent.ts) (lines 56-58), this ensures the state survives process restarts, migrations, or unexpected terminations. Developers should implement `loadState()` and `saveState()` methods to serialize and deserialize game data from the persistent store.