Realtime
Overview
Section titled “Overview”Realtime streams row-level changes from your Data tables to connected clients. Subscribe to inserts, updates, and deletes on specific tables—or filter to rows matching a policy—without polling.
Realtime builds on the same Identity and policy model as Data. Every subscription is authenticated; the Rust policy engine decides which change events each subscriber receives. Transport is Server-Sent Events (SSE) first, with a WebSocket adapter for Supabase JS client compatibility.
Use Realtime for live dashboards, collaborative editors, notification feeds, and any UI that should reflect database state within milliseconds of a write.
Key features
Section titled “Key features”- Table subscriptions — Listen to all changes on a table or filter by column values.
- Policy-aware delivery — Subscribers only receive events for rows they could read via Data API.
- SSE with resume — Reconnect with
Last-Event-IDto catch up after network blips. - WebSocket adapter — Drop-in compatibility layer for existing Supabase realtime clients.
- Org-scoped channels — Active org from JWT +
X-Reactor-Orgscopes all subscriptions. - Low overhead — Built on Postgres logical decoding; no application-level polling.
Quickstart
Section titled “Quickstart”Subscribe to new todos in your organization and log each insert.
reactor auth login user@example.com --password '...'reactor realtime subscribe todos --events insert --org acmeimport { createClient } from '@reactor/client';
const reactor = createClient({ url: process.env.REACTOR_URL! });await reactor.auth.signInWithPassword({ email, password });reactor.auth.setOrg('acme');
const channel = reactor.realtime.channel('todos');
channel.on('insert', (payload) => { console.log('New todo:', payload.new);});
await channel.subscribe();curl -N "$REACTOR_URL/data/v1/todos/subscribe?events=insert" \ -H "Authorization: Bearer $REACTOR_TOKEN" \ -H "X-Reactor-Org: acme" \ -H "Accept: text/event-stream"Expected SSE event shape:
event: insertdata: {"table":"todos","schema":"public","new":{"id":"...","title":"Ship v1","done":false}}
event: heartbeatdata: {"ts":"2026-05-29T12:00:00Z"}How-to guides
Section titled “How-to guides”Filter subscriptions to specific rows
Section titled “Filter subscriptions to specific rows”Only receive events when a column matches—useful for per-user or per-room feeds.
const channel = reactor.realtime .channel('messages') .on('insert', (payload) => appendMessage(payload.new)) .filter('room_id=eq.my-room-id');
await channel.subscribe();curl -N "$REACTOR_URL/data/v1/messages/subscribe?events=insert&room_id=eq.my-room-id" \ -H "Authorization: Bearer $REACTOR_TOKEN" \ -H "X-Reactor-Org: acme" \ -H "Accept: text/event-stream"Resume after disconnect
Section titled “Resume after disconnect”Pass the last received event ID to avoid missing changes during reconnection.
let lastEventId: string | undefined;
const channel = reactor.realtime.channel('orders');channel.on('*', (payload, meta) => { lastEventId = meta.eventId; handleChange(payload);});
await channel.subscribe({ lastEventId });curl -N "$REACTOR_URL/data/v1/orders/subscribe" \ -H "Authorization: Bearer $REACTOR_TOKEN" \ -H "X-Reactor-Org: acme" \ -H "Accept: text/event-stream" \ -H "Last-Event-ID: evt_01HZ8K..."Subscribe to multiple event types
Section titled “Subscribe to multiple event types”Listen for inserts, updates, and deletes on the same table.
const channel = reactor.realtime.channel('todos');
channel .on('insert', ({ new: row }) => addTodo(row)) .on('update', ({ old, new: row }) => updateTodo(old.id, row)) .on('delete', ({ old }) => removeTodo(old.id));
await channel.subscribe();reactor realtime subscribe todos --events insert,update,delete --org acmeConfiguration
Section titled “Configuration”[data]# Realtime shares the Data service and database connectionmigrations_dir = "./migrations"user_schema = "public"
[realtime]enabled = trueheartbeat_interval_secs = 30max_subscriptions_per_connection = 20replay_buffer_secs = 300 # How long Last-Event-ID replay is availableEnvironment variables:
| Variable | Default | Description |
|---|---|---|
REACTOR_DATA_DATABASE_URL | — | Same Postgres as Data |
REACTOR_REALTIME_ENABLED | true | Toggle realtime endpoints |
REACTOR_REALTIME_HEARTBEAT_SECS | 30 | SSE keepalive interval |
REACTOR_REALTIME_MAX_SUBSCRIPTIONS | 20 | Per-connection channel limit |
REACTOR_REALTIME_REPLAY_BUFFER_SECS | 300 | Event replay window |
Limits and quotas
Section titled “Limits and quotas”| Limit | Default | Notes |
|---|---|---|
| Subscriptions per connection | 20 | Additional channels require a new connection |
| Replay buffer | 5 minutes | Events older than this require a full resync |
| Max payload size | 64 KiB | Large row changes may be truncated with a fetch hint |
| Heartbeat interval | 30 seconds | Keeps proxies from closing idle SSE connections |
| Concurrent connections per org | Plan-dependent | Contact support for high fan-out workloads |
| WebSocket fallback | Same limits | Shares policy and quota enforcement with SSE |
Event types:
| Event | Payload |
|---|---|
insert | { new: Row } |
update | { old: Row, new: Row } |
delete | { old: Row } |
heartbeat | { ts: ISO8601 } |
API and SDK links
Section titled “API and SDK links”- HTTP base path:
/data/v1/{table}/subscribe(SSE) - WebSocket path:
/realtime/v1/websocket(Supabase-compatible protocol) - OpenAPI reference: Data API
- JavaScript SDK:
reactor.realtime - CLI:
reactor realtime
Related capabilities:
- Data — CRUD and policies that govern which events you receive
- Identity — Authentication required for all subscriptions
Troubleshooting
Section titled “Troubleshooting”Connection closes immediately (401)
Section titled “Connection closes immediately (401)”Missing or expired JWT. Refresh the access token and reconnect. Realtime does not support anonymous subscriptions.
reactor.auth.onAuthStateChange(async (event) => { if (event === 'TOKEN_REFRESHED') { await channel.unsubscribe(); await channel.subscribe(); }});No events received despite writes
Section titled “No events received despite writes”- Confirm the writing client uses the same org context (
X-Reactor-Org) - Check Data policies—the subscriber must have
data:{table}:readon affected rows - Verify Realtime is enabled:
GET /data/v1/healthshould reportrealtime: ok
Missing events after reconnect
Section titled “Missing events after reconnect”If Last-Event-ID is older than the replay buffer, perform a full Data query to resync state, then resubscribe without the header.
Proxy or CDN buffering SSE
Section titled “Proxy or CDN buffering SSE”Some proxies buffer text/event-stream. Disable buffering for realtime paths or connect directly to the Reactor API origin. Heartbeats every 30 seconds help keep connections alive through most intermediaries.