Core concepts
Understand rooms, presence, broadcasts, storage, connections, and permissions.
Rooms
A room is an isolated collaboration boundary identified by a string such as
document:8f7d or canvas:launch-plan.
Everyone connected to the same room receives the same presence, broadcasts, and durable storage updates. Users in different rooms cannot see one another.
Choose room IDs that map to an authorization boundary in your application:
const roomId = `organization:${organizationId}:document:${documentId}`;Your token endpoint should parse or look up that identifier and verify access before signing it.
Pick the right channel
Three kinds of state travel through a room. Choosing correctly is the single most important modelling decision in a collaborative feature.
| Presence | Broadcasts | Storage | |
|---|---|---|---|
| Lifetime | While the connection is open | The instant it is sent | Durable |
| Delivered to later joiners | Yes, as a snapshot | No | Yes |
| Survives a reconnect | No | No | Yes |
| Supports undo and redo | No | No | Yes |
| Typical use | Cursors, selections, tool state | Reactions, notifications | Document content |
Presence
Presence is ephemeral state associated with a connection:
type Presence = {
cursor: { x: number; y: number } | null;
selectedBlockId: string | null;
name: string;
};Presence is ideal for cursors, selections, activity, viewport, or tool state. It is removed when the connection leaves and should not contain durable application data.
One user can hold several connections at once — a second tab is a second connection with its own
presence. Identity comes from userId; connectionId identifies the browser tab.
Broadcasts
Broadcasts are transient events sent to every connection in a room:
type Events = {
reaction: { emoji: string; x: number; y: number };
presentationStarted: { slideId: string };
};Use broadcasts when only the moment matters. They are not replayed to people who join later.
Your own broadcasts come back to you
The realtime service fans a broadcast out to every connection in the room, including the sender.
If a listener should ignore locally-originated events, compare event.connectionId with
useConnectionSnapshot().connectionId. Otherwise a reaction can be counted twice on the sender's
screen. The broadcasts guide has a live demonstration.
Storage
Storage is durable JSON synchronized between clients and persisted by PolynomialHQ:
type Storage = {
blocks: Array<{
id: string;
text: string;
x: number;
y: number;
}>;
title: string;
};Storage updates use Yjs under the hood, survive reconnects, and are delivered to later room
connections. Mutate storage through useMutation() or a room's createMutation().
initialStorage is a seed, not a reset. The first connection to an empty room writes it; every
later connection receives the persisted document instead.
Connections and snapshots
A room exposes a current snapshot and emits events as it changes:
const snapshot = room.getSnapshot();
snapshot.connection.status;
snapshot.presence;
snapshot.others;
snapshot.storage;
snapshot.storageStatus;
snapshot.history;React hooks subscribe to the same room state through useSyncExternalStore, so components update
when relevant room events arrive.
Roles
A room token contains one of three roles:
read: join, receive state, and publish presence.write: everything inread, plus broadcasts and storage updates.admin: full room access for privileged clients.
Roles are enforced by the realtime service, not by the browser. See Authentication for an interactive comparison.
Room-management requests do not use a room token. They are authenticated with the secret key on your backend.
How the pieces fit together
Your backend decides who may enter a room and signs a short-lived token. The browser uses that token to open a room connection. Presence and broadcasts travel between active connections, while storage updates are also persisted for the next connection.
The React quickstart shows this complete request and connection flow in one small application.