# Realtime

> Subscribe to record changes over WebSocket or SSE.

_Updated: 2026-06-10_

Railbase pushes record changes to subscribed clients in real time over WebSocket
(or Server-Sent Events). Subscriptions respect the same access rules as the REST
API — you only receive events for records you could read.

## Connect and subscribe

The WebSocket endpoint is `GET /api/realtime/ws` (subprotocol `railbase.v1`).
Subscribe by sending a JSON frame with the topics you care about:

```json
{ "action": "subscribe", "topics": ["posts/*", "posts/abc123", "users/me"] }
```

You can `subscribe` / `unsubscribe` at any time without reconnecting.

For SSE, open `GET /api/realtime?topics=posts/*,orders/create` instead — the
topics ride in the query string, and the connection also speaks the
PocketBase-SDK dialect (a `PB_CONNECT` hello with a `clientId`, then
`POST /api/realtime` to update that client's subscriptions). Both transports
require an authenticated caller.

## Topics

Topics are `<collection>/<verb>` or `<collection>/<recordId>`:

| Topic | Matches |
|---|---|
| `posts/*` | every create/update/delete on `posts` |
| `posts/create` | only new `posts` |
| `posts/abc123` | changes to one record |

Verbs are `create`, `update`, and `delete`.

## Events

The server pushes one frame per matching record event; `data` carries the verb
and the record:

```json
{
  "event": "posts/create",
  "id": "<event_id>",
  "data": { "action": "create", "record": { "id": "abc123", "title": "…" } }
}
```

plus control frames (`railbase.subscribed`, `pong`, `error`). Over SSE the same
triple maps onto the `event:` / `id:` / `data:` frame fields.

> [!NOTE]
> A subscription is filtered by the collection's **ListRule**, evaluated per
> subscriber — so realtime can't leak records a user couldn't list over REST.
> Subscribe-time `filter:` / `expand:` are not supported yet; subscribe to a topic
> and read details over REST if you need expansion.

## From the SDK

The generated [TypeScript client](typescript-sdk) wraps all of this:

```ts
const sub = rb.realtime.subscribe({ topics: ["posts/*"] }, (evt) => {
  console.log(evt.event, evt.data);
});
// later: sub.unsubscribe()
```

## Publishing your own topics

Server-side (Go), plugins and apps can publish domain events on the bus:

```go
app.Realtime().Publish("orders.shipped", payload, tenantID)
```

Subscribers receive them the same way as record events.
