Skip to main content
Y-S2 is a Cloudflare Worker that serves Yjs documents over WebSockets, using S2 as the durable, ordered log of each room’s updates and an R2 bucket for snapshots. Any number of Worker instances can serve the same room: they coordinate only through the room’s S2 stream, so there is no central server to run or scale.

How it works

  • Rooms — each room is a stream named rooms/<room>/index in your basin, created on first use.
  • Writes — document updates and presence (cursor) updates from a client are batched and appended to the room’s stream. Messages larger than S2’s 1 MiB record limit, such as a large paste, are stored in R2 and the stream carries a pointer to them.
  • Fan-out — every connection reads the room’s stream and relays new records to its client.
  • Joining — a new connection loads the room’s latest snapshot from R2, reads the stream from where that snapshot ends, and sends the client the full document once it has caught up.
  • Snapshots — while a room is active, connections periodically write a new snapshot to R2 and then trim the stream up to it. A lease built on fencing tokens ensures only one connection snapshots a room at a time.
See Behind y-s2: serverless multiplayer rooms for a deeper walkthrough.

Prerequisites

  • A Cloudflare account on the Workers Paid plan. Each WebSocket connection is a single Worker invocation, and every S2 and R2 call it makes counts toward the per-invocation subrequest limit, which is 50 on the free plan.
  • An R2 bucket for snapshots and large messages.
  • An S2 basin with Create stream on append and Create stream on read enabled, and a scoped access token for it.
  • Node.js 22 or newer.
Set the basin’s default stream retention to infinite, or at least longer than a room may go without edits. Snapshots are only taken while a room is active, so updates that expire from the stream before a snapshot covers them are lost. Infinite retention requires a payment method on your S2 account (see limits).

Deploy

  1. Clone the repository and install dependencies:
  2. Point the R2_BUCKET binding in wrangler.jsonc at your bucket by setting its bucket_name.
  3. Store your S2 credentials as Worker secrets:
  4. Deploy:
For local development, put S2_ACCESS_TOKEN and S2_BASIN in a .dev.vars file and run npm run dev. The repository also includes a GitHub Actions workflow that checks and deploys every push to main; see its README for the secrets it expects.

Configuration

These optional variables can be set under vars in wrangler.jsonc (or in .dev.vars locally):

Client integration

Connect with the y-websocket provider. The server URL ends in /ws, and the room is passed both as the room name and as the room parameter:
Presence works through the provider’s awareness, as with any y-websocket server.
Y-S2 does not authenticate clients yet: any non-empty yauth value is accepted, and /auth/token returns a fixed demo token. Add your own authorization before using it for anything but demos. Anyone who can reach the Worker can read and write any room, and create new rooms in your basin.

Resources