Realtime

Broadcast

Send and receive messages using Realtime Broadcast


Let's explore how to implement Realtime Broadcast to send messages between clients using either WebSockets, REST API or triggers from your database.

Usage

You can use the Supabase client libraries to send and receive Broadcast messages.

Initialize the client

Go to your Supabase project's API Settings and grab the URL and anon public API key.


_10
import { createClient } from '@supabase/supabase-js'
_10
_10
const SUPABASE_URL = 'https://<project>.supabase.co'
_10
const SUPABASE_KEY = '<your-anon-key>'
_10
_10
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)

Listening to broadcast messages

You can provide a callback for the broadcast channel to receive message. This example will receive any broadcast messages in room-1:


_16
// Join a room/topic. Can be anything except for 'realtime'.
_16
const channelA = supabase.channel('room-1')
_16
_16
// Simple function to log any messages we receive
_16
function messageReceived(payload) {
_16
console.log(payload)
_16
}
_16
_16
// Subscribe to the Channel
_16
channelA
_16
.on(
_16
'broadcast',
_16
{ event: 'test' },
_16
(payload) => messageReceived(payload)
_16
)
_16
.subscribe()

Sending broadcast messages

We can send Broadcast messages using channelB.send(). Let's set up another client to send messages.


_16
// Join a room/topic. Can be anything except for 'realtime'.
_16
const channelB = supabase.channel('room-1')
_16
_16
channelB.subscribe((status) => {
_16
// Wait for successful connection
_16
if (status !== 'SUBSCRIBED') {
_16
return null
_16
}
_16
_16
// Send a message once the client is subscribed
_16
channelB.send({
_16
type: 'broadcast',
_16
event: 'test',
_16
payload: { message: 'hello, world' },
_16
})
_16
})

Before sending messages we need to ensure the client is connected, which we have done within the subscribe() callback.

Broadcast options

You can pass configuration options while initializing the Supabase Client.

Self-send messages

By default, broadcast messages are only sent to other clients. You can broadcast messages back to the sender by setting Broadcast's self parameter to true.


_20
const myChannel = supabase.channel('room-2', {
_20
config: {
_20
broadcast: { self: true },
_20
},
_20
})
_20
_20
myChannel.on(
_20
'broadcast',
_20
{ event: 'test-my-messages' },
_20
(payload) => console.log(payload)
_20
)
_20
_20
myChannel.subscribe((status) => {
_20
if (status !== 'SUBSCRIBED') { return }
_20
channelC.send({
_20
type: 'broadcast',
_20
event: 'test-my-messages',
_20
payload: { message: 'talking to myself' },
_20
})
_20
})

Acknowledge messages

You can confirm that Realtime received your message by setting Broadcast's ack config to true.


_17
const myChannel = supabase.channel('room-3', {
_17
config: {
_17
broadcast: { ack: true },
_17
},
_17
})
_17
_17
myChannel.subscribe(async (status) => {
_17
if (status !== 'SUBSCRIBED') { return }
_17
_17
const serverResponse = await myChannel.send({
_17
type: 'broadcast',
_17
event: 'acknowledge',
_17
payload: {},
_17
})
_17
_17
console.log('serverResponse', serverResponse)
_17
})

Use this to guarantee that the server has received the message before resolving channelD.send's promise. If the ack config is not set to true when creating the channel, the promise returned by channelD.send will resolve immediately.

Send messages using REST calls

You can also send a Broadcast message by making an HTTP request to Realtime servers. This is useful when you want to send messages from your server or client without having to first establish a WebSocket connection.


_15
const channel = supabase.channel('test-channel')
_15
_15
// No need to subscribe to channel
_15
_15
channel
_15
.send({
_15
type: 'broadcast',
_15
event: 'test',
_15
payload: { message: 'Hi' },
_15
})
_15
.then((resp) => console.log(resp))
_15
_15
// Remember to clean up the channel
_15
_15
supabase.removeChannel(channel)

Trigger broadcast messages from your database

How it works

Broadcast Changes allows you to trigger messages from your database. To achieve it Realtime is directly reading your WAL (Write Append Log) file using a publication against the realtime.messages table so whenever a new insert happens a message is sent to connected users.

It uses partitioned tables per day which allows the deletion your previous images in a performant way by dropping the physical tables of this partitioned table. Tables older than 3 days old are deleted.

Broadcasting from the database works like a client-side broadcast, using WebSockets to send JSON packages. Realtime Authorization is required and enabled by default to protect your data.

The database broadcast feature provides two functions to help you send messages:

  • realtime.send will insert a message into realtime.messages without a specific format.
  • realtime.broadcast_changes will insert a message with the required fields to emit database changes to clients. This helps you set up triggers on your tables to emit changes.

Broadcasting a message from your database

The realtime.send function provides the most flexibility by allowing you to broadcast messages from your database without a specific format. This allows you to use database broadcast for messages that aren't necessarily tied to the shape of a Postgres row change.


_10
SELECT realtime.send (
_10
to_jsonb ('{}'::text), -- JSONB Payload
_10
'event', -- Event name
_10
'topic', -- Topic
_10
FALSE -- Public / Private flag
_10
);

Broadcast record changes

Setup realtime authorization

Realtime Authorization is required and enabled by default. To allow your users to listen to messages from topics, create a RLS (Row Level Security) policy:


_10
CREATE POLICY "authenticated can receive broadcasts"
_10
ON "realtime"."messages"
_10
FOR SELECT
_10
TO authenticated
_10
USING ( true );

See the Realtime Authorization docs to learn how to set up more specific policies.

Set up trigger function

First, set up a trigger function that uses realtime.broadcast_changes to insert an event whenever it is triggered. The event is set up to include data on the schema, table, operation, and field changes that triggered it.

For this example use case, we want to have a topic with the name topic:<record id> to which we're going to broadcast events.


_14
CREATE OR REPLACE FUNCTION public.your_table_changes() RETURNS trigger AS $$
_14
BEGIN
_14
PERFORM realtime.broadcast_changes(
_14
'topic:' || NEW.id::text, -- topic
_14
TG_OP, -- event
_14
TG_OP, -- operation
_14
TG_TABLE_NAME, -- table
_14
TG_TABLE_SCHEMA, -- schema
_14
NEW, -- new record
_14
OLD -- old record
_14
);
_14
RETURN NULL;
_14
END;
_14
$$ LANGUAGE plpgsql;

Of note are the Postgres native trigger special variables used:

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

You can read more about them in this guide.

Set up trigger

Next, set up a trigger so the function runs whenever your target table has a change.


_10
CREATE TRIGGER broadcast_changes_for_your_table_trigger
_10
AFTER INSERT OR UPDATE OR DELETE ON public.your_table
_10
FOR EACH ROW
_10
EXECUTE FUNCTION your_table_changes ();

As you can see, it will be broadcasting all operations so our users will receive events when records are inserted, updated or deleted from public.your_table .

Listen on client side

Finally, client side will requires to be set up to listen to the topic topic:<record id> to receive the events.


_10
const gameId = 'id'
_10
await supabase.realtime.setAuth() // Needed for Realtime Authorization
_10
const changes = supabase
_10
.channel(`topic:${gameId}`)
_10
.on('broadcast', { event: 'INSERT' }, (payload) => console.log(payload))
_10
.on('broadcast', { event: 'UPDATE' }, (payload) => console.log(payload))
_10
.on('broadcast', { event: 'DELETE' }, (payload) => console.log(payload))
_10
.subscribe()