Docs

Publish and subscribe

The publish subscribe pattern on Foony channels, from one publisher to any number of subscribers.

Channels implement the publish subscribe pattern. A message published to a channel reaches every connection subscribed to it, in order, usually within a few milliseconds.

Channels

Get a channel by name. Instances are stable, so calling get twice with the same name returns the same channel.

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

Channel names are 1 to 255 characters from A-Z a-z 0-9 : - _, and must not start with a colon. Use colons for hierarchy, like chat:rooms:42, which pairs with capability patterns like chat:*. Dots are not allowed. Invalid names are rejected with error 40001.

Subscribe

Subscribe to everything on a channel, to one message name, or to a set of names. Subscribing attaches the channel automatically.

const off = channel.subscribe((message) => {
  console.log(message.name, message.data);
});

channel.subscribe('typing', onTyping);
channel.subscribe(['added', 'removed'], onChange);

off();
channel.unsubscribe();

Every subscribe call returns an unsubscribe function. channel.unsubscribe() with no arguments removes all message listeners, and channel.detach() stops receiving from the channel entirely.

Publish

await channel.publish('chat', { text: 'hello' });

The first argument is the message name subscribers can filter on, the second is any JSON-serializable payload. The promise resolves once the service has accepted the message. Whether the message is also stored for history depends on the channel’s channel rule.

While the connection is down or still connecting, publishes are queued and flushed on connect (queueMessages: true is the default).

Batching

Publish an array to send several messages as one atomic batch. Subscribers still receive them as individual messages.

await channel.publish([
  { name: 'added', data: { id: 1 } },
  { name: 'added', data: { id: 2 } },
]);

Single publish calls are also batched automatically per channel and flushed on a short throttle, which keeps rapid-fire publishes cheap. Call channel.flush() to force the buffer out immediately.

Ephemeral messages

Ephemeral messages skip storage entirely: they are not written to history and never replayed, which makes them the lowest-latency option for high-frequency, disposable state like cursor positions.

await channel.publish('cursor', { x, y }, { ephemeral: true });

The live demo on the homepage moves cursors, click waves, and drawn strokes exactly this way.

The message object

Subscribers receive a message object with these fields:

Field Type Meaning
name string The message name given to publish
data unknown The payload, decrypted when the channel has a cipher
clientId string? Who published it, when known
timestamp number Publish time in ms since epoch
messageId string Unique id, usable as a history cursor
ephemeral boolean? True for fire-and-forget messages

Delivery guarantees

  • Messages on one channel arrive in publish order.
  • Publishes carry a stable client-assigned id, so resends after a reconnect are deduplicated by the service. You will not see doubles.
  • The SDK detects sequence gaps after reconnects and backfills them from storage transparently.
  • Ephemeral messages are at-most-once by design: if you were not connected when one was sent, it is gone.