0
# Real-time Subscriptions
1
2
WebSocket-based real-time functionality for database changes, broadcast messages, and presence tracking. Enables live updates for collaborative applications, chat systems, and live data synchronization.
3
4
## Capabilities
5
6
### Channel Management
7
8
Create and manage real-time channels for different types of real-time communication.
9
10
```typescript { .api }
11
/**
12
* Creates a Realtime channel with Broadcast, Presence, and Postgres Changes
13
* @param name - The name of the Realtime channel
14
* @param opts - Channel configuration options
15
* @returns RealtimeChannel instance for subscribing to events
16
*/
17
channel(name: string, opts?: RealtimeChannelOptions): RealtimeChannel;
18
19
/**
20
* Returns all Realtime channels
21
* @returns Array of all active RealtimeChannel instances
22
*/
23
getChannels(): RealtimeChannel[];
24
25
/**
26
* Unsubscribes and removes Realtime channel from Realtime client
27
* @param channel - The RealtimeChannel instance to remove
28
* @returns Promise resolving to operation result
29
*/
30
removeChannel(channel: RealtimeChannel): Promise<'ok' | 'timed out' | 'error'>;
31
32
/**
33
* Unsubscribes and removes all Realtime channels from Realtime client
34
* @returns Promise resolving to array of operation results
35
*/
36
removeAllChannels(): Promise<('ok' | 'timed out' | 'error')[]>;
37
38
interface RealtimeChannelOptions {
39
config?: {
40
/** Enable postgres changes on this channel */
41
postgres_changes?: {
42
enabled?: boolean;
43
};
44
/** Enable presence on this channel */
45
presence?: {
46
key?: string;
47
};
48
/** Enable broadcast on this channel */
49
broadcast?: {
50
self?: boolean;
51
ack?: boolean;
52
};
53
};
54
}
55
56
interface RealtimeChannel {
57
/** Channel topic/name */
58
topic: string;
59
/** Channel state */
60
state: 'closed' | 'errored' | 'joined' | 'joining' | 'leaving';
61
62
/** Subscribe to events on this channel */
63
on(
64
type: 'postgres_changes' | 'broadcast' | 'presence',
65
filter: Record<string, any>,
66
callback: RealtimeCallback
67
): RealtimeChannel;
68
69
/** Subscribe to the channel */
70
subscribe(callback?: (status: string, err?: Error) => void): RealtimeChannel;
71
72
/** Unsubscribe from the channel */
73
unsubscribe(): Promise<'ok' | 'timed out' | 'error'>;
74
75
/** Send a broadcast message */
76
send(options: {
77
type: 'broadcast';
78
event: string;
79
payload?: Record<string, any>;
80
}): Promise<'ok' | 'error' | 'timed out'>;
81
82
/** Track presence */
83
track(state: Record<string, any>): Promise<'ok' | 'error' | 'timed out'>;
84
85
/** Untrack presence */
86
untrack(): Promise<'ok' | 'error' | 'timed out'>;
87
}
88
89
type RealtimeCallback = (payload: any) => void;
90
```
91
92
**Usage Examples:**
93
94
```typescript
95
// Create a channel
96
const channel = supabase.channel('room-1');
97
98
// Create a channel with configuration
99
const channel = supabase.channel('room-1', {
100
config: {
101
broadcast: { self: true, ack: true },
102
presence: { key: 'user-id' }
103
}
104
});
105
106
// Get all channels
107
const channels = supabase.getChannels();
108
console.log('Active channels:', channels.length);
109
110
// Remove a specific channel
111
await supabase.removeChannel(channel);
112
113
// Remove all channels
114
await supabase.removeAllChannels();
115
```
116
117
### Database Change Subscriptions
118
119
Listen for real-time changes to your PostgreSQL database tables.
120
121
```typescript { .api }
122
interface PostgresChangesFilter {
123
/** The database event to listen for */
124
event: '*' | 'INSERT' | 'UPDATE' | 'DELETE';
125
/** The database schema name */
126
schema: string;
127
/** The table name (optional) */
128
table?: string;
129
/** Additional filter criteria */
130
filter?: string;
131
}
132
133
interface PostgresChangesPayload {
134
/** The database schema */
135
schema: string;
136
/** The table name */
137
table: string;
138
/** The commit timestamp */
139
commit_timestamp: string;
140
/** The type of event */
141
eventType: 'INSERT' | 'UPDATE' | 'DELETE';
142
/** New record data (for INSERT and UPDATE) */
143
new?: Record<string, any>;
144
/** Old record data (for UPDATE and DELETE) */
145
old?: Record<string, any>;
146
/** The primary key columns and values */
147
old_record?: Record<string, any>;
148
}
149
150
/**
151
* Listen for database changes on a channel
152
* @param type - Must be 'postgres_changes'
153
* @param filter - Database change filter configuration
154
* @param callback - Function to handle the change payload
155
* @returns RealtimeChannel instance for chaining
156
*/
157
on(
158
type: 'postgres_changes',
159
filter: PostgresChangesFilter,
160
callback: (payload: PostgresChangesPayload) => void
161
): RealtimeChannel;
162
```
163
164
**Usage Examples:**
165
166
```typescript
167
// Listen to all changes on a table
168
const channel = supabase
169
.channel('db-changes')
170
.on(
171
'postgres_changes',
172
{ event: '*', schema: 'public', table: 'posts' },
173
(payload) => {
174
console.log('Change received!', payload);
175
console.log('Event type:', payload.eventType);
176
console.log('New data:', payload.new);
177
console.log('Old data:', payload.old);
178
}
179
)
180
.subscribe();
181
182
// Listen to specific events
183
const channel = supabase
184
.channel('new-posts')
185
.on(
186
'postgres_changes',
187
{ event: 'INSERT', schema: 'public', table: 'posts' },
188
(payload) => {
189
console.log('New post created:', payload.new);
190
}
191
)
192
.subscribe();
193
194
// Listen with column filters
195
const channel = supabase
196
.channel('published-posts')
197
.on(
198
'postgres_changes',
199
{
200
event: 'UPDATE',
201
schema: 'public',
202
table: 'posts',
203
filter: 'status=eq.published'
204
},
205
(payload) => {
206
console.log('Post published:', payload.new);
207
}
208
)
209
.subscribe();
210
211
// Listen to multiple tables
212
const channel = supabase
213
.channel('user-activity')
214
.on(
215
'postgres_changes',
216
{ event: '*', schema: 'public', table: 'users' },
217
(payload) => {
218
console.log('User change:', payload);
219
}
220
)
221
.on(
222
'postgres_changes',
223
{ event: '*', schema: 'public', table: 'profiles' },
224
(payload) => {
225
console.log('Profile change:', payload);
226
}
227
)
228
.subscribe();
229
```
230
231
### Broadcast Messaging
232
233
Send and receive real-time messages between clients connected to the same channel.
234
235
```typescript { .api }
236
interface BroadcastFilter {
237
/** The broadcast event name to listen for */
238
event: string;
239
}
240
241
interface BroadcastPayload {
242
/** The broadcast event name */
243
event: string;
244
/** The broadcast payload data */
245
payload?: Record<string, any>;
246
/** The type of broadcast */
247
type: 'broadcast';
248
}
249
250
/**
251
* Listen for broadcast messages on a channel
252
* @param type - Must be 'broadcast'
253
* @param filter - Broadcast filter configuration
254
* @param callback - Function to handle the broadcast payload
255
* @returns RealtimeChannel instance for chaining
256
*/
257
on(
258
type: 'broadcast',
259
filter: BroadcastFilter,
260
callback: (payload: BroadcastPayload) => void
261
): RealtimeChannel;
262
263
/**
264
* Send a broadcast message to all subscribers of the channel
265
* @param options - Broadcast message options
266
* @returns Promise resolving to send result
267
*/
268
send(options: {
269
type: 'broadcast';
270
event: string;
271
payload?: Record<string, any>;
272
}): Promise<'ok' | 'error' | 'timed out'>;
273
```
274
275
**Usage Examples:**
276
277
```typescript
278
// Listen for broadcast messages
279
const channel = supabase
280
.channel('chat-room')
281
.on(
282
'broadcast',
283
{ event: 'message' },
284
(payload) => {
285
console.log('New message:', payload.payload);
286
}
287
)
288
.subscribe();
289
290
// Send broadcast messages
291
await channel.send({
292
type: 'broadcast',
293
event: 'message',
294
payload: {
295
user: 'john_doe',
296
text: 'Hello everyone!',
297
timestamp: new Date().toISOString()
298
}
299
});
300
301
// Listen for multiple broadcast events
302
const channel = supabase
303
.channel('game-room')
304
.on(
305
'broadcast',
306
{ event: 'player-move' },
307
(payload) => {
308
console.log('Player moved:', payload.payload);
309
}
310
)
311
.on(
312
'broadcast',
313
{ event: 'game-state' },
314
(payload) => {
315
console.log('Game state updated:', payload.payload);
316
}
317
)
318
.subscribe();
319
320
// Send different types of messages
321
await channel.send({
322
type: 'broadcast',
323
event: 'player-move',
324
payload: { playerId: 'player-1', position: { x: 10, y: 20 } }
325
});
326
327
await channel.send({
328
type: 'broadcast',
329
event: 'game-state',
330
payload: { status: 'playing', score: 100 }
331
});
332
```
333
334
### Presence Tracking
335
336
Track which users are currently online and share their status or state information.
337
338
```typescript { .api }
339
interface PresenceFilter {
340
/** The presence event to listen for */
341
event: 'sync' | 'join' | 'leave';
342
}
343
344
interface PresencePayload {
345
/** The presence event type */
346
event: 'sync' | 'join' | 'leave';
347
/** The current presence state for all users */
348
newPresences?: Record<string, any>;
349
/** The presence state for users who left */
350
leftPresences?: Record<string, any>;
351
}
352
353
/**
354
* Listen for presence events on a channel
355
* @param type - Must be 'presence'
356
* @param filter - Presence filter configuration
357
* @param callback - Function to handle the presence payload
358
* @returns RealtimeChannel instance for chaining
359
*/
360
on(
361
type: 'presence',
362
filter: PresenceFilter,
363
callback: (payload: PresencePayload) => void
364
): RealtimeChannel;
365
366
/**
367
* Track user presence state
368
* @param state - The state object to track for this user
369
* @returns Promise resolving to track result
370
*/
371
track(state: Record<string, any>): Promise<'ok' | 'error' | 'timed out'>;
372
373
/**
374
* Stop tracking user presence
375
* @returns Promise resolving to untrack result
376
*/
377
untrack(): Promise<'ok' | 'error' | 'timed out'>;
378
```
379
380
**Usage Examples:**
381
382
```typescript
383
// Track user presence
384
const channel = supabase
385
.channel('online-users', {
386
config: {
387
presence: {
388
key: 'user-id'
389
}
390
}
391
})
392
.on(
393
'presence',
394
{ event: 'sync' },
395
() => {
396
const newState = channel.presenceState();
397
console.log('Presence sync:', newState);
398
399
const users = Object.keys(newState).map(key => newState[key][0]);
400
console.log('Online users:', users);
401
}
402
)
403
.on(
404
'presence',
405
{ event: 'join' },
406
({ newPresences }) => {
407
console.log('User joined:', newPresences);
408
}
409
)
410
.on(
411
'presence',
412
{ event: 'leave' },
413
({ leftPresences }) => {
414
console.log('User left:', leftPresences);
415
}
416
)
417
.subscribe();
418
419
// Track current user
420
await channel.track({
421
user_id: 'user-123',
422
username: 'john_doe',
423
status: 'online',
424
last_seen: new Date().toISOString()
425
});
426
427
// Update user state
428
await channel.track({
429
user_id: 'user-123',
430
username: 'john_doe',
431
status: 'typing',
432
last_seen: new Date().toISOString()
433
});
434
435
// Stop tracking
436
await channel.untrack();
437
```
438
439
### Channel Lifecycle
440
441
```typescript
442
// Subscribe with callback to handle connection status
443
const channel = supabase
444
.channel('my-channel')
445
.on('postgres_changes', { event: '*', schema: 'public', table: 'posts' }, handleChange)
446
.subscribe((status, err) => {
447
if (status === 'SUBSCRIBED') {
448
console.log('Successfully subscribed to channel');
449
}
450
if (status === 'CHANNEL_ERROR') {
451
console.error('Channel error:', err);
452
}
453
if (status === 'TIMED_OUT') {
454
console.error('Channel timed out');
455
}
456
if (status === 'CLOSED') {
457
console.log('Channel closed');
458
}
459
});
460
461
// Check channel state
462
console.log('Channel state:', channel.state); // 'closed' | 'errored' | 'joined' | 'joining' | 'leaving'
463
464
// Unsubscribe
465
const result = await channel.unsubscribe();
466
console.log('Unsubscribe result:', result); // 'ok' | 'timed out' | 'error'
467
```
468
469
## Advanced Real-time Patterns
470
471
### Combining Multiple Real-time Features
472
473
```typescript
474
// Create a comprehensive chat room with all features
475
const chatChannel = supabase
476
.channel('chat-room-1', {
477
config: {
478
broadcast: { self: true, ack: true },
479
presence: { key: 'user_id' }
480
}
481
})
482
// Listen for new messages in database
483
.on(
484
'postgres_changes',
485
{ event: 'INSERT', schema: 'public', table: 'messages' },
486
(payload) => {
487
console.log('New message in DB:', payload.new);
488
}
489
)
490
// Listen for typing indicators
491
.on(
492
'broadcast',
493
{ event: 'typing' },
494
(payload) => {
495
console.log('User typing:', payload.payload);
496
}
497
)
498
// Track user presence
499
.on(
500
'presence',
501
{ event: 'sync' },
502
() => {
503
const state = chatChannel.presenceState();
504
const onlineUsers = Object.keys(state);
505
console.log('Online users:', onlineUsers);
506
}
507
)
508
.subscribe();
509
510
// Track user as online
511
await chatChannel.track({
512
user_id: currentUser.id,
513
username: currentUser.username,
514
avatar_url: currentUser.avatar_url,
515
status: 'online'
516
});
517
518
// Send typing indicator
519
await chatChannel.send({
520
type: 'broadcast',
521
event: 'typing',
522
payload: {
523
user_id: currentUser.id,
524
is_typing: true
525
}
526
});
527
```
528
529
### Error Handling and Reconnection
530
531
```typescript
532
const channel = supabase
533
.channel('robust-channel')
534
.on('postgres_changes', { event: '*', schema: 'public', table: 'data' }, handleChange)
535
.subscribe((status, err) => {
536
switch (status) {
537
case 'SUBSCRIBED':
538
console.log('Channel subscribed successfully');
539
setConnectionStatus('connected');
540
break;
541
case 'CHANNEL_ERROR':
542
console.error('Channel error:', err);
543
setConnectionStatus('error');
544
// Implement retry logic
545
setTimeout(() => {
546
channel.subscribe();
547
}, 5000);
548
break;
549
case 'TIMED_OUT':
550
console.error('Channel subscription timed out');
551
setConnectionStatus('timeout');
552
break;
553
case 'CLOSED':
554
console.log('Channel closed');
555
setConnectionStatus('disconnected');
556
break;
557
}
558
});
559
560
// Check connection status periodically
561
setInterval(() => {
562
if (channel.state === 'errored' || channel.state === 'closed') {
563
console.log('Attempting to reconnect...');
564
channel.subscribe();
565
}
566
}, 10000);
567
```
568
569
### Performance Optimization
570
571
```typescript
572
// Use specific filters to reduce unnecessary messages
573
const optimizedChannel = supabase
574
.channel('optimized')
575
.on(
576
'postgres_changes',
577
{
578
event: 'UPDATE',
579
schema: 'public',
580
table: 'posts',
581
filter: 'status=eq.published AND author_id=eq.123'
582
},
583
(payload) => {
584
// Only receive updates for published posts by specific author
585
console.log('Relevant update:', payload.new);
586
}
587
)
588
.subscribe();
589
590
// Batch presence updates to avoid excessive network calls
591
let presenceUpdateTimeout: NodeJS.Timeout;
592
const updatePresence = (newState: Record<string, any>) => {
593
clearTimeout(presenceUpdateTimeout);
594
presenceUpdateTimeout = setTimeout(() => {
595
channel.track(newState);
596
}, 1000); // Batch updates every second
597
};
598
```