PolynomialHQdocs
Guides

Canvas example

Run the repository's multi-client canvas and trace presence, broadcasts, and persisted Yjs updates.

The repository includes a browser canvas for testing multiple realtime clients against a local API and worker. Unlike the demos elsewhere in this documentation, it runs against real infrastructure: Postgres, Redis, the API, and the realtime worker.

Start backing services

From the repository root:

pnpm install
cp .env.example .env
docker compose up -d postgres redis
pnpm db:migrate
pnpm db:seed

Start the API and worker

Run each in its own terminal:

pnpm --filter @polynomialhq/api dev
pnpm --filter @polynomialhq/worker dev

The API listens on http://0.0.0.0:4000 and the worker on ws://0.0.0.0:4001/v1/realtime. Both expose a /health endpoint you can curl to confirm they are up.

Start the example

pnpm --filter @polynomialhq/example-canvas dev

Open http://localhost:5173 in two browser windows.

Exercise the room

  1. Choose the same room ID in both windows.
  2. Select a different display name in each.
  3. Connect both windows.
  4. Move a pointer over the canvas to send presence.
  5. Add a block to persist a Yjs update.
  6. Reload one window and verify that the block returns from storage.

How authorization works

The example's development server uses PolynomialServer to issue a token:

const polynomial = new PolynomialServer({ secretKey });

const token = polynomial.authorizeRoom({
  projectId,
  role: "write",
  roomId,
  userId,
  userInfo: { name },
});

In production, the userId and the room permission must come from your application's authenticated session, never from URL parameters. The example takes them from the query string precisely because it is a smoke test.

How the browser joins

The example speaks the versioned WebSocket protocol directly:

socket = new WebSocket(config.realtimeUrl);

socket.addEventListener("open", () => {
  socket.send(
    JSON.stringify({
      token,
      type: "room.join",
      v: 1,
    }),
  );
});

Application code should normally use @polynomialhq/client or @polynomialhq/react. The raw protocol is exposed here so the example can also act as a browser smoke test.

Presence flow

Pointer movement sends a presence.update message. Other clients receive presence.updated, while a new connection receives presence.sync with the current room snapshot.

Storage flow

The browser stores blocks in a Yjs document and sends encoded updates:

send({
  encoding: "base64",
  type: "yjs.update",
  update: bytesToBase64(update),
  v: 1,
});

The worker persists the update before fanout. A new connection receives yjs.sync, applies the persisted updates, and reconstructs the same block map.

Try a fresh room

Use New room to select a unique room ID. Both browser windows must join that new ID to share state. The previous room's persisted blocks remain isolated.

Scale-out check

With REDIS_URL configured, the worker fans room traffic out through Redis pub/sub so several worker processes can serve the same room. The repository ships an integration test for that path:

pnpm --filter @polynomialhq/worker test:integration

Run it with Redis up to confirm cross-instance presence, broadcast, and storage delivery.

On this page