PolynomialHQdocs
SDKs

@polynomialhq/server

Token signing, room management, storage inspection, and server events for trusted Node.js backends.

pnpm add @polynomialhq/server

This package uses node:crypto and belongs in a trusted Node.js runtime. Never import it into a browser bundle or an edge runtime without Node APIs.

PolynomialServer

import { PolynomialServer } from "@polynomialhq/server";

const polynomial = new PolynomialServer({
  secretKey: process.env.POLYNOMIAL_SECRET_KEY!,
});

PolynomialServerOptions

Prop

Type

The constructor throws RoomTokenError if secretKey does not begin with sk_.

Authorize a room

const token = polynomial.authorizeRoom({
  expiresInSeconds: 3600,
  metadata: { organizationId: "org_acme" },
  projectId: "project_123",
  role: "write",
  roomId: "document:42",
  userId: "user_123",
  userInfo: {
    avatar: "https://example.com/avatar.png",
    name: "Ada",
  },
});

AuthorizeRoomInput requires projectId, roomId, and userId. It also accepts expiresInSeconds (default 1800), metadata, role (default "write"), and userInfo.

authorizeRoom() and identifyUser() sign locally with HMAC-SHA256 and return a string synchronously. They make no network request, so there is nothing to await and nothing to retry.

RoomRole is "read" | "write" | "admin".

Identify a user

const token = polynomial.identifyUser({
  expiresInSeconds: 3600,
  groupIds: ["team_design"],
  organizationId: "org_acme",
  projectId: "project_123",
  userId: "user_123",
  userInfo: { name: "Ada" },
});

IdentifyUserInput supports user, group, organization, project, expiry, and public user information. Only userId is required.

Manage rooms

All room-management methods are asynchronous, hit the management API, and are authenticated with the server secret as a bearer token.

createRoom

const room = await polynomial.createRoom({
  defaultRole: "write",
  metadata: { documentId: "doc_42" },
  projectId: "project_123",
  roomId: "document:42",
});

Accepts CreateRoomInput.

getRoom

const room = await polynomial.getRoom({
  projectId: "project_123",
  roomId: "document:42",
});

Accepts GetRoomInput. Throws PolynomialApiError with status: 404 when the room does not exist.

listRooms

const rooms = await polynomial.listRooms({
  projectId: "project_123",
});

Accepts ListRoomsInput and returns every room in the project.

updateRoom

const room = await polynomial.updateRoom({
  defaultRole: "read",
  metadata: { archived: true },
  projectId: "project_123",
  roomId: "document:42",
});

Accepts UpdateRoomInput. Fields left undefined are omitted from the request body rather than sent as nulls, so a partial update leaves the other fields untouched.

upsertRoom

const room = await polynomial.upsertRoom({
  create: {
    defaultRole: "write",
    metadata: { documentId: "doc_42" },
  },
  projectId: "project_123",
  roomId: "document:42",
  update: {
    metadata: { lastOpenedAt: new Date().toISOString() },
  },
});

Accepts UpsertRoomInput. create applies when the room is new and update when it already exists.

deleteRoom

const deletedRoom = await polynomial.deleteRoom({
  projectId: "project_123",
  roomId: "document:42",
});

Accepts DeleteRoomInput. Deletion is a soft delete: the returned room has state: "deleted" and a populated deletedAt.

RoomManagementRoom

Room-management methods return:

FieldType
idInternal room record ID
projectIdOwning project
roomIdApplication room identifier
defaultRoleRoomRole
metadataRecord<string, unknown>
stateRoomState ("active" | "deleted")
createdAtISO timestamp
updatedAtISO timestamp
deletedAtISO timestamp or null

roomId is the identifier your application and room tokens use. id is the internal record ID and should not be used for authorization.

Inspect persisted storage

const storage = await polynomial.getRoomStorage({
  projectId: "project_123",
  roomId: "document:42",
});

GetRoomStorageInput identifies the room. RoomStorageDocument contains:

  • snapshot: the latest YjsSnapshotDocument, or null;
  • updates: tail YjsUpdateDocument entries after the snapshot.

Both formats use base64 encoding and include byte length and sequence metadata.

This returns the persisted Yjs representation, not the decoded application JSON. To read the document server-side, apply the snapshot and then the tail updates to a Y.Doc with the yjs package.

Broadcast from the server

await polynomial.broadcastEvent({
  event: {
    type: "export.completed",
    exportId: "export_123",
  },
  projectId: "project_123",
  roomId: "document:42",
});

BroadcastRoomEventInput contains a BroadcastRoomEvent, projectId, and roomId. The event object must have a type string; browser listeners receive it under that name.

Errors

PolynomialApiError

Management API failures throw PolynomialApiError, which includes the numeric HTTP status:

try {
  await polynomial.getRoom({ projectId, roomId });
} catch (error) {
  if (error instanceof PolynomialApiError && error.status === 404) {
    // Room does not exist.
  }
}

The message is taken from the API response's error field when present.

RoomTokenError

Invalid secret keys, malformed tokens, signature failures, and expired tokens throw RoomTokenError. This is a signing and verification error, never a transport error.

Token helpers

Use these functions when a class instance does not fit your integration:

createRoomToken

const token = createRoomToken({
  now: new Date(),
  projectId,
  role: "write",
  roomId,
  secretKey,
  userId,
});

Accepts AuthorizeRoomInput plus secretKey and optional now. now is the base for iat and exp, which makes tokens deterministic in tests.

verifyRoomToken

const claims = verifyRoomToken(token, {
  secretKey,
});

Returns RoomTokenClaims after checking the key ID, HMAC signature, schema, and expiry. The signature comparison is constant-time.

createIdentityToken and verifyIdentityToken

These mirror the room helpers for IdentifyUserInput and IdentityTokenClaims:

const identity = createIdentityToken({
  secretKey,
  userId: "user_123",
});

const claims = verifyIdentityToken(identity, { secretKey });

Room tokens and identity tokens are not interchangeable: they carry different aud claims and each verifier rejects the other kind.

Token schemas

The package exports Zod schemas for validation and introspection:

  • roomRoleSchema
  • roomStateSchema
  • roomTokenClaimsSchema
  • identityTokenClaimsSchema

On this page