or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

authentication.mddatabase.mdfunctions.mdindex.mdrealtime.mdstorage.md

realtime.mddocs/

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

```