new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Supabase · Realtime · all subjects

realtime/broadcast

69 notes in this subject, read out of this brain and free to use. This is page 1 of 2.

Broadcast WebSocket performance: 32,000 concurrent users

Using WebSocket to send Broadcast messages, the system achieved 32,000 concurrent users with 64,000 total channel joins, 224,000 msgs/sec throughput, median latency of 6 ms, p95 latency of 28 ms, and p99 latency of 213 ms. Data received was 6.4 MB/s (7.9 GB total) and data sent was 23 KB/s (28 MB total). New connection rate was 320 conn/sec and channel join rate was 640 joins/sec.

Large-scale broadcast: 250,000 concurrent users

At high scale with 250,000 concurrent users across 500,000 total channel joins (100 users per channel), the system achieved >800,000 msgs/sec throughput with median latency of 58 ms, p95 latency of 279 ms, and p99 latency of 508 ms. Data received was 68 MB/s (600 GB total) and data sent was 0.64 MB/s (5.7 GB total).

Broadcast 50KB payload with reduced load

With 2,000 concurrent users sending 50KB payloads at 14,000 msgs/sec, median latency was 19 ms, p95 39 ms, and p99 82 ms. Data received was 644 MB/s (176 GB total) and data sent was 192 MB/s (52 GB total).

Broadcast payload size impact on performance

Message payload size significantly affects broadcast performance. With 4,000 concurrent users and 28,000 msgs/sec: 1KB payload had median latency 13 ms, p95 16 ms, p99 85 ms; 10KB payload had median latency 16 ms, p95 42 ms, p99 93 ms; 50KB payload had median latency 27 ms, p95 81 ms, p99 146 ms. Data throughput increased from 31.2 MB/s received (1KB) to 268 MB/s (10KB) to 1284 MB/s (50KB).

Broadcast from database performance: 80,000 concurrent users

Using the realtime.broadcast_changes function to send Broadcast messages from the database with a trigger on every insert, the system achieved 80,000 concurrent users with 160,000 total channel joins and 10,000 msgs/sec throughput (10,000 inserts per second in database). Median latency was 46 ms, p95 latency was 132 ms, and p99 latency was 159 ms. Data received was 1.7 MB/s (42 GB total) and data sent was 0.4 MB/s (4 GB total). New connection rate was 2000 conn/sec and channel join rate was 4000 joins/sec.

Example: Kotlin client creating private broadcast channel

```kotlin val channel = supabase.channel("room-1") { isPrivate = true } channel.broadcastFlow<MyPayload>(event = "test").onEach { println(it) }.launchIn(scope) // launch in your coroutine scope channel.subscribe(blockUntilSubscribed = true) println("Connected!") ``` This example shows how to create a private broadcast channel in Kotlin.

Broadcast messages have extension value 'broadcast' in realtime.messages

The extension field on the realtime.messages table records the message type. For Broadcast messages, the value of realtime.messages.extension is 'broadcast'. You can check for this value in your RLS policies.

Join Broadcast Channel requires at least one read or write permission

To join a Broadcast Channel, a user must have at least one read or write permission on the Channel topic. This is enforced through RLS policies on realtime.messages.

Example: RLS policy to allow sending broadcast messages on topic

```sql create policy "authenticated can send broadcast on topic" on "realtime"."messages" for insert to authenticated with check ( exists ( select user_id from rooms_users where user_id = (select auth.uid()) and room_topic = (select realtime.topic()) and realtime.messages.extension in ('broadcast') ) ); ``` This policy allows authenticated users who are linked to the requested topic in the rooms_users table to send broadcast messages.

Example: JavaScript client creating private broadcast channel

```javascript import { createClient } from '@supabase/supabase-js' const supabase = createClient('your_project_url', 'your_supabase_api_key') const channel = supabase.channel('room-1', { config: { private: true }, }) channel .on('broadcast', { event: 'test' }, (payload) => console.log(payload)) .subscribe((status, err) => { if (status === 'SUBSCRIBED') { console.log('Connected!') } else { console.error(err) } }) ``` This example shows how to create a private broadcast channel in JavaScript.

Example: Dart client creating private broadcast channel

```dart final channel = supabase.channel( 'room-1', opts: const RealtimeChannelConfig(private: true), ); channel .onBroadcast(event: 'test', callback: (payload) => print(payload)) .subscribe((status, err) { if (status == RealtimeSubscribeStatus.subscribed) { print('Connected!'); } else { print(err); } }); ``` This example shows how to create a private broadcast channel in Dart.

Example: Swift client creating private broadcast channel

```swift let channel = supabase.channel("room-1") { $0.isPrivate = true } Task { for await payload in channel.broadcastStream(event: "test") { print(payload) } } await channel.subscribe() print("Connected!") ``` This example shows how to create a private broadcast channel in Swift.

Example: Python client creating private broadcast channel

```py channel = realtime.channel( "room-1", {"config": {"private": True}} ) await channel.on_broadcast( "test", callback=lambda payload: print(payload) ).subscribe( lambda state, err: ( print("Connected") if state == RealtimeSubscribeStates.SUBSCRIBED else print(err) ) ) ``` This example shows how to create a private broadcast channel in Python.

Broadcast message via client library TypeScript example

Listen for messages with channel.on('broadcast', { event: 'message_sent' }, (payload) => { console.log('New message:', payload.payload) }).subscribe(). Send a message with channel.send({ type: 'broadcast', event: 'message_sent', payload: { text: 'Hello, world!', user: 'john_doe', timestamp: new Date().toISOString() } }).

realtime.broadcast_changes trigger function

Use realtime.broadcast_changes() in a trigger function to broadcast full database changes with metadata. Syntax: PERFORM realtime.broadcast_changes('topic_name', TG_OP, TG_OP, TG_TABLE_NAME, TG_TABLE_SCHEMA, NEW, OLD). This sends the full database change and is best for mirroring database changes.

Broadcast message via HTTP REST API

Send messages via HTTP POST to https://<project>.supabase.co/rest/v1/rpc/broadcast with Content-Type: application/json and apikey header set to SECRET_KEY. Request body: { topic: 'room:lobby:messages', event: 'message_sent', payload: { text: 'Hello from server!', user: 'system', timestamp: ISO string }, private: true }.

Create database trigger for broadcasting changes

Create a trigger function with CREATE OR REPLACE FUNCTION broadcast_message_changes() RETURNS TRIGGER, then create the trigger with CREATE TRIGGER messages_broadcast_trigger AFTER INSERT OR UPDATE OR DELETE ON messages FOR EACH ROW EXECUTE FUNCTION broadcast_message_changes(). Use SECURITY DEFINER to allow the function to perform realtime operations.

When to use Broadcast

Use Broadcast for real-time messaging and notifications, custom events and game state, database change notifications with triggers, high-frequency updates like cursor tracking, and most use cases.

Three ways to send messages with Realtime

Messages can be sent via client libraries using channel.send() with broadcast type, via HTTP REST API POST requests to the broadcast endpoint, or using database triggers with realtime.broadcast_changes() or realtime.send() functions.

realtime.send trigger function for custom notifications

Use realtime.send() in a trigger function to send custom payloads with full control over what data is broadcast. Syntax: PERFORM realtime.send(jsonb_payload, event_name, topic_name, is_private). This allows custom notifications and filtered data, best for selective notifications.

Realtime limits by plan - broadcast replay retention

Broadcast replay retention limits are 72 hours for all plans: Free 72 hours, Pro 72 hours, Pro (no spend cap) 72 hours, Team 72 hours, Enterprise 72 hours.

Realtime limits by plan - broadcast replay messages per request

Broadcast replay messages per request limit is 25 for all plans: Free 25, Pro 25, Pro (no spend cap) 25, Team 25, Enterprise 25.

Broadcast changes trigger setup

Example setting up trigger on table: CREATE TRIGGER broadcast_changes_for_your_table_trigger AFTER INSERT OR UPDATE OR DELETE ON public.your_table FOR EACH ROW EXECUTE FUNCTION your_table_changes ();. Broadcasts all operations so users receive events when records are inserted, updated or deleted from the table.

Broadcast changes RLS policy requirement

Create an RLS policy to allow authenticated users to receive broadcasts: CREATE POLICY "authenticated can receive broadcasts" ON "realtime"."messages" FOR SELECT TO authenticated USING ( true );

PostgreSQL trigger special variables used in broadcast functions

TG_OP is the operation that triggered the function. TG_TABLE_NAME is the table that caused the trigger. TG_TABLE_SCHEMA is the schema of the table that caused the trigger invocation. NEW is the record after the change. OLD is the record before the change.

Broadcast replay configuration options

Broadcast Replay enables private channels to access messages sent earlier. Only messages published via Broadcast From the Database are available for replay. Configuration options: since (Required) - The epoch timestamp in milliseconds (e.g., 1697472000000), specifying the earliest point from which messages should be retrieved. limit (Optional) - The number of messages to return, must be a positive integer with maximum value of 25.

Broadcast replay message retention window

Messages are stored in daily partitions, and partitions older than 72 hours are dropped. Because whole days are removed at once, a message stays available for at least 72 hours and at most 4 days, depending on the time of day it was sent. Setting since further back than the retained window does not recover deleted messages.

JavaScript broadcast replay configuration

Example broadcast replay configuration in JavaScript (available from client version 2.74.0+): const config = { private: true, broadcast: { replay: { since: 1697472000000, limit: 10 } } }; const channel = supabase.channel('main:room', { config }); channel.on('broadcast', { event: 'position' }, (payload) => { if (payload?.meta?.replayed) { console.log('Replayed message: ', payload) } else { console.log('This is a new message', payload) } }).subscribe();

Dart broadcast replay configuration

Example broadcast replay configuration in Dart (available from client version 2.10.0+): final channel = supabase.channel('my-channel', RealtimeChannelConfig(self: true, ack: true, private: true, replay: ReplayOption(since: 1697472000000, limit: 25))); channel.onBroadcast(event: 'position', callback: (payload) { final meta = payload['meta'] as Map<String, dynamic>?; if (meta?['replayed'] == true) { print('Replayed message: ${meta?['id']}'); } }).subscribe();

Swift broadcast replay configuration

Example broadcast replay configuration in Swift (available from client version 2.34.0+): let channel = supabase.realtimeV2.channel("my-channel") { $0.isPrivate = true; $0.broadcast.acknowledgeBroadcasts = true; $0.broadcast.receiveOwnBroadcasts = true; $0.broadcast.replay = ReplayOption(since: 1697472000000, limit: 25) }; channel.onBroadcast(event: "position") { message in if let meta = message["payload"]?.objectValue?["meta"]?.objectValue, let replayed = meta["replayed"]?.boolValue, replayed { print("Replayed message: \(meta["id"]?.stringValue ?? "")") } }; await channel.subscribe();

Python broadcast replay configuration

Example broadcast replay configuration in Python (available from client version 2.22.0+): channel = client.channel('my-channel', { 'config': { "private": True, 'broadcast': { 'self': True, 'ack': True, 'replay': { 'since': 1697472000000, 'limit': 100 } } } }); def on_broadcast(payload): if payload.get('meta', {}).get('replayed'): print(f"Replayed message: {payload['meta']['id']}"); await channel.on_broadcast('position', on_broadcast); await channel.subscribe();

Broadcast replay common use cases

Common use cases for Broadcast Replay include: displaying the most recent messages from a chat room, loading the last events that happened during a sports event, ensuring users always see the latest events after a page reload or network interruption, and highlighting the most recent sections that changed in a web page.

How Broadcast Changes from database works

Broadcast Changes allows triggering messages from the database. Realtime directly reads the Write-Ahead Log (WAL) file using a publication against the realtime.messages table. Whenever a new insert occurs, a message is sent to connected users. It works like client-side broadcast, using WebSockets to send JSON payloads. Realtime Authorization is required and enabled by default to protect data.

realtime.broadcast_changes() function for broadcasting table changes

The realtime.broadcast_changes() function inserts a message with required fields to emit database changes to clients. It helps set up triggers on tables to emit changes. Parameters: topic, event/operation, operation (TG_OP), table name, schema name, new record, old record.

Dart client send broadcast without subscription

Example showing broadcast send in Dart without subscription: final channel = supabase.channel('test-channel'); final res = await channel.sendBroadcastMessage(event: "test", payload: { 'message': 'Hi' }); print(res);

Broadcast messaging paths: REST, WebSocket, and database

Broadcast messages can be sent three ways. REST API receives an HTTP request then sends a message via WebSocket to connected clients. Client libraries send a message via WebSocket to the server, which then sends a message via WebSocket to connected clients. Database method adds a new entry to realtime.messages where logical replication listens for changes, then sends a message via WebSocket to connected clients.

Public vs private broadcast flag behavior

The public flag in realtime.send(payload, event, topic, is_private) only affects who can subscribe to the topic, not who can read messages from the database. Public (false) means anyone can subscribe to that topic without authentication. Private (true) means only authenticated clients can subscribe to that topic. The Realtime service always connects to the database as the authenticated Supabase Admin role regardless of public/private setting.

Broadcast authorization verification process

For authorization, the system inserts a message and tries to read it, then rolls back the transaction to verify that the Row Level Security (RLS) policies set by the user are being respected by the user joining the channel. This message is not sent to the user; it is only used for authorization verification.

Binary payload support in broadcast

Broadcast payloads can be binary (ArrayBuffer or ArrayBufferView, e.g. Uint8Array) over WebSocket from supabase-js 2.91.0 and supabase-swift 2.44.0 and later. Binary payloads sent to clients running older SDK versions are silently dropped and never arrive over the WebSocket. The Dart, Kotlin, and Python clients do not support binary payloads yet.

Database broadcast message retention and storage

All messages sent using Broadcast from the Database are stored in the realtime.messages table and will be deleted after 3 days. The messages are stored in partitioned tables per day, which allows performant deletion of previous messages by dropping the physical tables. Tables older than 3 days are automatically deleted.

Public and private broadcast matching requirement

For broadcasts to work correctly, the public/private setting on the database broadcast must match the setting on the client channel. A public broadcast only reaches public channels and a private broadcast only reaches private channels. By default, all database broadcasts are private. If the database sends a public message but the client subscribes to a private channel, the message is not delivered because private channels only accept signed, authenticated messages.

realtime.send() function for database broadcasts

The realtime.send() function sends messages directly from the database. Signature: select realtime.send(jsonb_build_object('hello', 'world'), 'event', 'topic', false); Parameters: JSONB Payload, Event name string, Topic string, Public/Private flag boolean.

realtime.send_binary() function for database binary broadcasts

To broadcast a binary payload from the database, use realtime.send_binary() function with a bytea payload. Signature: select realtime.send_binary('\x012345'::bytea, 'event', 'topic', true); Parameters: bytea payload, Event name string, Topic string, Private/Public flag boolean (defaults to true). Binary broadcasts only reach channels with the same private setting. Binary messages only reach clients on supabase-js 2.91.0 and supabase-swift 2.44.0 or later; older clients silently drop them.

REST API endpoints for broadcast

Single message endpoint: POST /realtime/v1/api/broadcast/{topic}/events/{event} with Content-Type header determining payload type (application/json for JSON, application/octet-stream for binary). Add ?private=true to broadcast to a private channel. Batch endpoint also available: POST /realtime/v1/api/broadcast accepts JSON body with messages array (JSON payloads only). Requires apikey header with SUPABASE_TOKEN.

Broadcasting before vs after WebSocket subscription

Sending a message before subscribing to the channel will use HTTP. Sending a message after subscribing to the channel will use WebSockets.

JavaScript client broadcast subscription with event filtering

Example showing how to receive broadcast messages with event filtering in JavaScript: const myChannel = supabase.channel('test-channel'); myChannel.on('broadcast', { event: 'shout' }, (payload) => messageReceived(payload)).subscribe(); The event filter can be '*' to listen to all events instead of a specific event name.

JavaScript client send broadcast message after WebSocket subscription

Example showing how to send a broadcast message after WebSocket subscription in JavaScript: myChannel.subscribe((status) => { if (status !== 'SUBSCRIBED') { return null }; myChannel.send({ type: 'broadcast', event: 'shout', payload: { message: 'Hi' } }) }). Also shows sending binary payload: myChannel.send({ type: 'broadcast', event: 'cursor-pos', payload: new Uint8Array([1, 2, 3]).buffer })

Dart client broadcast subscription

Example showing broadcast subscription in Dart: final myChannel = supabase.channel('test-channel'); myChannel.onBroadcast(event: 'shout', callback: (payload) => messageReceived(payload)).subscribe();

Swift client broadcast subscription

Example showing broadcast subscription in Swift: let myChannel = await supabase.channel("test-channel"); let broadcastStream = await myChannel.broadcast(event: "shout"); await myChannel.subscribe(); for await event in broadcastStream { print(event) }

Kotlin client broadcast subscription

Example showing broadcast subscription in Kotlin: val myChannel = supabase.channel("test-channel"); val broadcastFlow: Flow<JsonObject> = myChannel.broadcastFlow<JsonObject>("shout").onEach { println(it) }.launchIn(yourCoroutineScope); myChannel.subscribe();

Python client broadcast subscription

Example showing broadcast subscription in Python: my_channel = supabase.channel('test-channel'); def message_received(payload): print(f"Broadcast received: {payload}"); await my_channel.on_broadcast('shout', message_received).subscribe();

C# client broadcast subscription and sending

Example showing broadcast in C#: class ShoutBroadcast : BaseBroadcast { [JsonProperty("message")] public string Message { get; set; } }; var myChannel = supabase.Realtime.Channel("test-channel"); var broadcast = myChannel.Register<ShoutBroadcast>(); broadcast.AddBroadcastEventHandler((sender, _) => { Console.WriteLine(broadcast.Current()); }); await myChannel.Subscribe(); await broadcast.Send("shout", new ShoutBroadcast { Message = "Hi" });

Broadcast self option to receive own messages

By default, broadcast messages are only sent to other clients. Set the self/receiveOwnBroadcasts parameter to true to receive broadcast messages from the sender. In JavaScript: config: { broadcast: { self: true } }. In Dart: RealtimeChannelConfig(self: true). In Swift: $0.broadcast.receiveOwnBroadcasts = true. In Kotlin: broadcast { receiveOwnBroadcasts = true }. In Python: {"config": {"broadcast": {"self": True}}}. In C#: myChannel.Register<ShoutBroadcast>(broadcastSelf: true).

Broadcast acknowledge option for server confirmation

Set Broadcast's ack/acknowledgeBroadcasts/broadcastAck parameter to true to confirm that the Realtime servers have received the message. In JavaScript: config: { broadcast: { ack: true } }. In Dart: RealtimeChannelConfig(ack: true). In Swift: $0.broadcast.acknowledgeBroadcasts = true. In Kotlin: broadcast { acknowledgeBroadcasts = true }. In C#: myChannel.Register<ShoutBroadcast>(broadcastAck: true). If ack is not set to true, the promise returned by send() will resolve immediately without waiting for server acknowledgement. Python does not support this yet.

JavaScript channel.httpSend() for REST broadcast without WebSocket subscription

Available from Supabase JavaScript client version 2.107.0 and later. httpSend() always uses the REST API regardless of WebSocket connection state. No need to subscribe to the channel first. ArrayBuffer and ArrayBufferView payloads are sent as application/octet-stream; all other payloads are JSON-encoded. Example: await channel.httpSend('cursor-pos', { x: Math.random(), y: Math.random() }); or await channel.httpSend('cursor-pos', new Uint8Array([1, 2, 3]).buffer);

JavaScript broadcast authorization trigger function setup

Example trigger function that broadcasts record changes: CREATE OR REPLACE FUNCTION public.your_table_changes() RETURNS trigger SECURITY DEFINER SET search_path = '' AS $$ BEGIN PERFORM realtime.broadcast_changes( 'topic:' || NEW.id::text, TG_OP, TG_OP, TG_TABLE_NAME, TG_TABLE_SCHEMA, NEW, OLD ); RETURN NULL; END; $$ LANGUAGE plpgsql;

realtime.broadcast_changes() function

Supabase Realtime provides a realtime.broadcast_changes() function that can be used in conjunction with a Postgres trigger to automatically send messages when a record is created, updated, or deleted. This function uses a private channel and requires broadcast authorization RLS policies to be met.

Broadcast trigger function example

Example trigger function for broadcasting database changes: create or replace function public.your_table_changes() returns trigger security definer language plpgsql as $$ begin perform realtime.broadcast_changes('topic:' || coalesce(NEW.id, OLD.id) ::text, TG_OP, TG_OP, TG_TABLE_NAME, TG_TABLE_SCHEMA, NEW, OLD); return null; end; $$; The function parameters are: topic (built using record id), event (TG_OP), operation (TG_OP), table (TG_TABLE_NAME), schema (TG_TABLE_SCHEMA), new record (NEW), and old record (OLD).

Create trigger for broadcast changes

Example trigger to run the broadcast function after any changes to a table: create trigger handle_your_table_changes after insert or update or delete on public.your_table for each row execute function your_table_changes ();

Client-side subscription to broadcast changes

Example JavaScript code to listen to broadcast changes on the client side: import { createClient } from '@supabase/supabase-js'; const supabase = createClient('your_project_url', 'your_supabase_api_key'); const gameId = 'id'; await supabase.realtime.setAuth(); const changes = supabase.channel(`topic:${gameId}`, { config: { private: true } }).on('broadcast', { event: 'INSERT' }, (payload) => console.log(payload)).on('broadcast', { event: 'UPDATE' }, (payload) => console.log(payload)).on('broadcast', { event: 'DELETE' }, (payload) => console.log(payload)).subscribe(); The channel must be configured as private (config: { private: true }) and setAuth() must be called for Realtime Authorization.

Give your agent this brain