Skip to main content

Subscriptions

Listen to real-time database changes with onSnapshot. Use client.db(namespace).table(name) to access the correct DB block.

Table Subscription

const unsubscribe = client.db('shared').table('posts').onSnapshot((event) => {
if (event.type === 'added') {
console.log('New post:', event.data);
} else if (event.type === 'modified') {
console.log('Updated:', event.data);
} else if (event.type === 'removed') {
console.log('Deleted:', event.data);
}
});

// Stop listening
unsubscribe();

Document Subscription

Subscribe to changes on a single document:

const unsubscribe = client.db('shared').table('posts').doc('post-id').onSnapshot((event) => {
console.log('Document changed:', event.data);
});

When a change occurs, both the table-level subscription and the document-level subscription receive the event simultaneously (dual propagation).

Filtered Subscriptions

Filter events using where(). EdgeBase supports two filtering modes:

Client-Side Filtering (Default)

The SDK receives all events and filters locally. No additional configuration needed:

const unsubscribe = client.db('shared').table('posts')
.where('status', '==', 'published')
.onSnapshot((event) => {
// Only receives events for published posts
});

Server-Side Filtering

For high-traffic tables, enable server-side filtering so the server only sends matching events. This reduces bandwidth and client-side processing:

const unsubscribe = client.db('shared').table('posts')
.where('status', '==', 'published')
.onSnapshot((event) => {
// Server only sends events where status == 'published'
}, { serverFilter: true });

Server-side filters support AND conditions, OR conditions, 8 comparison operators, and runtime updates. See Server-Side Filters for the full guide.

Admin SDKs

Realtime subscriptions are only available in client SDKs (JavaScript, Dart, Swift, Kotlin, C#, C++). Server-only Admin SDKs do not support onSnapshot. Use server-side broadcast instead.

Event Types

Every onSnapshot callback receives an event with one of three types:

TypeDescriptionevent.data
addedNew document createdFull document
modifiedExisting document updatedFull document (after update)
removedDocument deletednull

Authentication

WebSocket connections require JWT authentication. The SDK handles this automatically — after connecting, it sends an auth message before any subscriptions. See Architecture for protocol details.

Key behaviors:

  • Timeout: 5000ms default (configurable via realtime.authTimeoutMs)
  • Token refresh: The SDK automatically sends refreshed tokens. The server re-evaluates all subscriptions and revokes any that are no longer authorized.
  • subscription_revoked event: Listen globally to handle permission changes:
client.realtime.on('subscription_revoked', ({ channel }) => {
console.warn('Subscription revoked for channel:', channel);
});

See Access Rules for the full re-auth flow.

Token Refresh and Revoked Channels

When a client's auth token is refreshed on a long-lived WebSocket connection, the server re-evaluates channel access. The response includes any channels the client lost access to:

{
"type": "auth_refreshed",
"userId": "user-123",
"revokedChannels": ["db:private-table"]
}
FieldDescription
revokedChannelsList of channels the client lost access to after token refresh
  • The client should handle this by removing subscriptions for revoked channels
  • This occurs when user roles or permissions change while the client is connected
  • If no channels are revoked, revokedChannels is an empty array
  • If the refresh fails, existing auth is preserved and a non-fatal error is returned

Batch Changes

When many changes occur simultaneously (e.g., bulk operations), the server batches them into a single batch_changes message instead of individual events. The batch threshold is 10 changes by default (configurable via realtime.batchThreshold).

{
"type": "batch_changes",
"channel": "realtime:shared:posts",
"changes": [
{ "event": "modified", "docId": "post_1", "data": { "title": "Updated" } },
{ "event": "modified", "docId": "post_2", "data": { "title": "Also updated" } }
],
"total": 150
}

Your onSnapshot callback receives each change individually — the SDK unpacks batch events automatically.

SDK Version Negotiation

The SDK sends its version during authentication (sdkVersion field). The server uses this to determine batch support:

  • Modern SDKs: Receive batch_changes messages (unpacked automatically by the SDK)
  • Legacy SDKs: Continue to receive individual events (backward compatible)

High-Frequency Update Pattern

For scenarios with very frequent updates (e.g., real-time analytics), consider debouncing your UI updates:

let pending: SnapshotEvent[] = [];

client.db('shared').table('metrics').onSnapshot((event) => {
pending.push(event);
requestAnimationFrame(() => {
if (pending.length > 0) {
renderUpdates(pending);
pending = [];
}
});
});

Type-Safe Subscriptions

Use the generic parameter on onSnapshot<T>() with types generated by npx edgebase typegen:

import type { Post } from './edgebase.d.ts';

// Typed subscription — change.data is Post | null
const unsub = client.realtime.onSnapshot<Post>('posts', (change) => {
console.log(change.data?.title); // ✅ TypeScript autocomplete
});

The edgebase typegen command generates interfaces from your edgebase.config.ts schema:

npx edgebase typegen -o edgebase.d.ts

This creates types like:

export interface Post {
id: string;
createdAt: string;
updatedAt: string;
title: string;
content?: string;
}

Keepalive (Ping/Pong)

The SDK sends periodic ping messages to keep the WebSocket connection alive and update the server's lastSeen timestamp for presence tracking:

// Client sends:
{ "type": "ping" }

// Server responds:
{ "type": "pong" }
SettingValue
Recommended ping interval30 seconds
Presence TTL60 seconds (configurable via presenceTTL)
  • Each ping updates the lastSeen timestamp for the connection's presence entry
  • If no ping is received within the TTL window (default 60s), the connection's presence is cleaned up and presence_leave is broadcast with reason: "timeout"
  • The SDK handles ping/pong automatically -- no application code is needed

Connection Management

  • Auto-reconnect — Reconnects automatically on disconnection with exponential backoff
  • Namespace-aware — Use client.db(namespace, id?) to route subscriptions to the correct DB block
  • Tab token sync — Auth/token state is synchronized across browser tabs
  • Hibernation recovery — Idle WebSocket connections cost $0 via Cloudflare's Hibernation API. On wake-up, the server sends FILTER_RESYNC and PRESENCE_RESYNC messages; the SDK automatically re-registers filters and presence state. See Architecture for details.