Presence

Who is on a channel right now — enter, leave, update, and the events everyone else receives.

Announcing yourself

const channel = realtime.channels.get('chat:lobby');

channel.presence.enter({ status: 'online' });

enter uses the connection's clientId, so set one — the default random client-a3f9… is a member nobody can identify.

channel.presence.update({ status: 'away' });
channel.presence.leave();

enter, update and leave are fire-and-forget: they write a frame and return. The confirmation is the Presence event that comes back to everyone attached, including you.

A disconnect leaves automatically. Cleanup on socket close unregisters the connection and fans out the leave, so a closed laptop does not linger as a member forever.

Watching the channel

channel.presence.subscribe((member, action) => {
  console.log(member.clientId, action);
});

action is enter, leave or update. To narrow it:

channel.presence.subscribe(['enter', 'leave'], (member, action) => {
  console.log(member.clientId, action);
});

That filter is client-side, deliberately. Presence volume is per member, not per message, and a subscriber that only wants enter still needs the connection attached for the others — otherwise get() drifts out of date.

Who is here

const members = await channel.presence.get();

Each member is:

{
  clientId: 'alice',
  connectionId: 'conn-…',
  data: { status: 'online' },
  updatedAt: 1710000000000,
}

This asks the server, over the socket that is already open and already authenticated — it is not a REST call and it does not need the presence capability twice.

There is also a local map maintained from the Presence frames this client has received: enter and update upsert by clientId, leave deletes. It only reflects what arrived on this connection since it attached, so it is a cache of the events you saw, not a snapshot of the channel. When you want the truth, ask the server.

Attach without entering

A client can watch presence without being a member of it:

const channel = realtime.channels.get('chat:lobby');

channel.presence.subscribe((member, action) => { /* … */ });

Subscribing attaches the channel, which is the gate for presence delivery. You receive everyone else's enters and leaves and appear in nobody's member list — which is what a dashboard wants.

Presence history

The enter / leave / update log is available over REST:

const events = await channel.presence.history({ limit: 100 });

See history.

Capabilities

Presence operations need the presence capability on that channel. A token scoped to chat:* => ['subscribe'] can read messages there and will be refused on enter with a 403.

Note that a presence-only channel is still a channel: if you use the presence:chat:room-7 convention, grant presence on that name, not on chat:room-7.

Across nodes

Live presence events are held in each node's memory and forwarded to interested peers, so a subscriber on another node sees them. The REST snapshot instead reads a shared store written by the origin node — which means it is accurate across a cluster only when a shared store (Postgres) is configured. On a single node, both paths agree either way.

On this page