Skip to content

Realtime

WebSocket + SSE subscriptions for collection events.

vb.subscribe(collection, filter, cb) opens a long-lived realtime connection and routes server events to your callback. Backed by a WebSocket where supported, with automatic SSE fallback for environments that can’t open WS (some serverless platforms, certain corporate proxies).

const off = vb.subscribe("posts", "*", (ev) => {
console.log(ev.type, ev.record);
// ev.type: "create" | "update" | "delete"
// ev.record: T (typed via Schema generic); ev.id on delete
});
// later, drop just this subscription:
off();
vb.subscribe("posts", "p_42", (ev) => {
// only fires for record id = "p_42"
});

Pass an array to follow several records: vb.subscribe("posts", ["p_1", "p_2"], cb).

The server checks every event against your auth before sending it: the collection’s view_rule for each record, and its list_rule too when you subscribe to the whole collection ("*"). So a subscriber only gets records they could read through the API; filter further in your callback.

A singleton has no list, so its updates follow the view_rule alone, whichever way you subscribe (server 0.26.2+; older servers only sent vb.subscribe("site", "*") events to admins, so subscribe to the record’s id there).

Every event is delivered, however close together: three quick updates to a record are three callbacks. (SDK 0.7.1 and older dropped a second event for the same record and type within a second.)

const offPosts = vb.subscribe("posts", "*", onPost);
const offUsers = vb.subscribe("users", "*", onUser);
// Disconnect the realtime channel entirely (keeps auth):
vb.closeRealtime();

A single WS connection is shared across all vb.subscribe calls — the manager multiplexes topic subscriptions over the same socket. Each callback gets only its own collection’s events. To hear every collection, subscribe to "*": vb.subscribe("*", "*", cb), and switch on ev.collection.

The connection authenticates with the token in vb.client.authStore, and follows it:

  • When the token changes (a refresh, a new sign-in), the SDK sends the new one over the open connection. Signing out closes the connection so it reconnects as anonymous.
  • The server re-checks a connection’s token every minute (server 0.17+). If it no longer holds, the server sends auth_expired and treats the connection as anonymous; the SDK then refreshes once and re-authenticates. If the refresh fails, events that need auth stop arriving until the user signs in again.
  • A dropped connection reconnects with backoff and re-subscribes every topic.

Backoff: 250 ms, 500 ms, 1 s, 2 s, 5 s, then every 10 s, each ±20%. Reconnect retries forever; stop it with vb.closeRealtime().

If the WebSocket can’t open (an error, or no open within 5 s) and the runtime has EventSource (browsers; Bun and Node don’t), the SDK falls back to server-sent events at GET /api/v1/realtime + POST /api/v1/realtime for subscription updates. A dropped SSE stream reconnects with the same backoff (SDK 0.7.3+). The same subscribe API works either way — your code doesn’t change.

Each event is what your callback receives:

{ "type": "create", "collection": "posts", "record": { "id": "p_42", "title": "hi" } }
{ "type": "delete", "collection": "posts", "id": "p_42" }

A record in an event has no expand. To show related records, fetch the record again with vb.collection(name).get(id, { expand }). See Realtime concept for the full wire format.

  • A single server (read worker threads are on by default) delivers every event to every subscriber, and so do several servers sharing one Postgres database. Only the deprecated vaultbase cluster keeps subscriptions per worker; don’t use it with realtime.
  • There is no durable replay: events sent while the connection was down are not delivered after it reconnects. Refetch what you show after a reconnect.
  • Rules are evaluated server-side per event; rules that are expensive to evaluate cost CPU on the broadcast path.