Skip to content

Realtime

Butterbase provides real-time data change notifications via WebSocket connections. When enabled on a table, any INSERT, UPDATE, or DELETE is broadcast to connected clients.

configure_realtime({ app_id: "app_abc123", tables: ["messages", "notifications"] })

This installs database triggers that capture changes and broadcast them via pg_notify.

Connect to: wss://api.butterbase.ai/v1/{app_id}/realtime

Browser clients (recommended): Pass token as query parameter:

wss://api.butterbase.ai/v1/app_abc123/realtime?token=eyJhbG...

Node.js / server-side: Use Authorization header:

const ws = new WebSocket('wss://api.butterbase.ai/v1/app_abc123/realtime', {
headers: { Authorization: 'Bearer <jwt>' }
});
Auth methodRole assigned
End-user JWTbutterbase_user (RLS enforced)
API key (bb_sk_...)butterbase_service (sees all changes)
No authbutterbase_anon
// Client -> Server
{ "type": "subscribe", "table": "messages" }
{ "type": "unsubscribe", "table": "messages" }
// Server -> Client
{ "type": "connected", "app_id": "app_abc123", "role": "butterbase_user" }
{ "type": "subscribed", "table": "messages" }
{ "type": "change", "table": "messages", "op": "INSERT", "record": {...}, "old_record": null, "timestamp": "..." }
{ "type": "change", "table": "messages", "op": "UPDATE", "record": {...}, "old_record": {...}, "timestamp": "..." }
{ "type": "change", "table": "messages", "op": "DELETE", "record": null, "old_record": {...}, "timestamp": "..." }
{ "type": "heartbeat", "timestamp": "..." }
{ "type": "error", "message": "..." }
const token = 'eyJhbG...'; // end-user JWT
const ws = new WebSocket(`wss://api.butterbase.ai/v1/app_abc123/realtime?token=${token}`);
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'subscribe', table: 'messages' }));
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'change') {
console.log(msg.op, msg.table, msg.record);
}
};

Subscribe to a subset of changes:

{ "type": "subscribe", "table": "messages", "filter": { "channel_id": "abc" } }

Only changes where channel_id = 'abc' will be delivered. Filters match on exact column equality.

RLS is enforced on realtime events:

  • butterbase_user — Only receives changes for rows they can SELECT
  • butterbase_service — Receives all changes
  • butterbase_anon — Receives changes based on anon policies

Track who else is connected:

// Opt in with metadata
{ "type": "presence_track", "metadata": { "name": "Alice", "cursor": { "x": 10, "y": 20 } } }
// Update metadata (e.g. cursor moved)
{ "type": "presence_update", "metadata": { "cursor": { "x": 50, "y": 60 } } }

Server broadcasts to all presence-tracking clients:

{ "type": "presence_state", "clients": [{ "client_id": "...", "user_id": "...", "metadata": {...} }] }
{ "type": "presence_join", "client_id": "...", "user_id": "...", "metadata": {...} }
{ "type": "presence_update", "client_id": "...", "metadata": {...} }
{ "type": "presence_leave", "client_id": "...", "user_id": "..." }

Presence is in-memory only — it resets on server restart.

Deploy functions that fire when clients send custom events:

deploy_function({
name: "handle-chat",
code: "...",
trigger: { type: "websocket", config: { event: "chat_message" } }
})

Clients send events:

{ "type": "event", "event": "chat_message", "payload": { "text": "hello" } }

The function response is returned:

{ "type": "event_response", "event": "chat_message", "data": { "echo": { "text": "hello" } } }
  • Tables must exist before enabling realtime
  • The full row is sent on each change (no column filtering yet)
  • Events during LISTEN reconnection may be lost — clients should re-fetch state on reconnect