PolynomialHQdocs
Getting started

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.

PresenceBroadcastsStorage
LifetimeWhile the connection is openThe instant it is sentDurable
Delivered to later joinersYes, as a snapshotNoYes
Survives a reconnectNoNoYes
Supports undo and redoNoNoYes
Typical useCursors, selections, tool stateReactions, notificationsDocument 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 in read, 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.

On this page