Introduction
Supabase Realtime allows you to listen to database changes, broadcast messages, track presence, and sync data across clients in real-time. It’s built on Phoenix Channels and WebSockets.
Features
Postgres Changes
Listen to INSERT, UPDATE, and DELETE operations on your database tables.
Broadcast
Send ephemeral messages between clients without persisting to the database.
Presence
Track and synchronize shared state between clients (like online users).
WebSocket Endpoint
Connect to the Realtime server:
wss://<PROJECT_REF>.supabase.co/realtime/v1/websocket
Authentication
Authenticate using your API key and optional JWT token:
import { createClient } from '@supabase/supabase-js'
const supabase = createClient (
'https://<PROJECT_REF>.supabase.co' ,
'YOUR_ANON_KEY'
)
// Authentication is handled automatically by the client
Channels
Channels are the foundation of Realtime. Create a channel to subscribe to changes:
const channel = supabase . channel ( 'room-1' )
Unique identifier for the channel.
Channel Lifecycle
const channel = supabase . channel ( 'my-channel' )
// Subscribe to the channel
channel . subscribe (( status ) => {
if ( status === 'SUBSCRIBED' ) {
console . log ( 'Connected!' )
}
})
// Unsubscribe when done
await supabase . removeChannel ( channel )
Postgres Changes
Listen to database changes in real-time.
Listen to All Changes
const channel = supabase
. channel ( 'db-changes' )
. on (
'postgres_changes' ,
{
event: '*' ,
schema: 'public' ,
table: 'countries'
},
( payload ) => {
console . log ( 'Change received!' , payload )
}
)
. subscribe ()
The event type: INSERT, UPDATE, DELETE, or * for all.
The database schema (usually public).
The table name to listen to.
Payload Structure
{
commit_timestamp : "2024-03-04T10:00:00Z" ,
eventType : "INSERT" , // or UPDATE, DELETE
new : {
id : 1 ,
name : "Denmark" ,
code : "DK"
},
old : {}, // Empty for INSERT, contains old values for UPDATE/DELETE
schema : "public" ,
table : "countries"
}
The type of change: INSERT, UPDATE, or DELETE.
The new row data (for INSERT and UPDATE).
The old row data (for UPDATE and DELETE).
ISO 8601 timestamp of the change.
Listen to Specific Events
// Listen to inserts only
const channel = supabase
. channel ( 'inserts' )
. on (
'postgres_changes' ,
{
event: 'INSERT' ,
schema: 'public' ,
table: 'countries'
},
( payload ) => {
console . log ( 'New country:' , payload . new )
}
)
. subscribe ()
Filter Changes
Filter changes based on column values:
const channel = supabase
. channel ( 'filtered-changes' )
. on (
'postgres_changes' ,
{
event: '*' ,
schema: 'public' ,
table: 'countries' ,
filter: 'code=eq.US'
},
( payload ) => {
console . log ( 'US country changed:' , payload )
}
)
. subscribe ()
Filter expression in the format column=op.value.
Broadcast
Send and receive ephemeral messages between clients.
Send Messages
const channel = supabase . channel ( 'room-1' )
// Send a message
await channel . send ({
type: 'broadcast' ,
event: 'cursor-pos' ,
payload: { x: 100 , y: 200 }
})
The event name for the broadcast.
The data to broadcast to other clients.
Receive Messages
const channel = supabase
. channel ( 'room-1' )
. on (
'broadcast' ,
{ event: 'cursor-pos' },
( payload ) => {
console . log ( 'Cursor position:' , payload )
}
)
. subscribe ()
Presence
Track which users are online and sync shared state.
Track Presence
const channel = supabase . channel ( 'online-users' )
// Track this user's presence
await channel . track ({
user_id: 'user-1' ,
online_at: new Date (). toISOString ()
})
The state to track for this client.
Listen to Presence
const channel = supabase
. channel ( 'online-users' )
. on ( 'presence' , { event: 'sync' }, () => {
const state = channel . presenceState ()
console . log ( 'Online users:' , state )
})
. on ( 'presence' , { event: 'join' }, ({ newPresences }) => {
console . log ( 'User joined:' , newPresences )
})
. on ( 'presence' , { event: 'leave' }, ({ leftPresences }) => {
console . log ( 'User left:' , leftPresences )
})
. subscribe ( async ( status ) => {
if ( status === 'SUBSCRIBED' ) {
await channel . track ({ user: 'user-1' })
}
})
Presence Events
Fired when presence state is synchronized.
Fired when a new client joins the channel.
Fired when a client leaves the channel.
Multiple Listeners
Combine different listener types on a single channel:
const channel = supabase
. channel ( 'combined' )
. on (
'postgres_changes' ,
{ event: '*' , schema: 'public' , table: 'messages' },
( payload ) => console . log ( 'DB change:' , payload )
)
. on (
'broadcast' ,
{ event: 'typing' },
( payload ) => console . log ( 'User typing:' , payload )
)
. on (
'presence' ,
{ event: 'sync' },
() => console . log ( 'Presence synced' )
)
. subscribe ()
Row Level Security
Realtime respects Row Level Security policies:
-- Users can only receive updates for their own data
CREATE POLICY "Users can listen to own data"
ON messages
FOR SELECT
USING ( auth . uid () = user_id);
Only changes that the user has permission to see will be sent through Realtime.
Enable Realtime
Enable Realtime for a table:
-- Enable Realtime for a table
ALTER TABLE countries REPLICA IDENTITY FULL;
-- Or via the Supabase Dashboard:
-- Database > Replication > Select your table > Enable Realtime
Tables must have REPLICA IDENTITY FULL enabled to receive UPDATE and DELETE events with old values.
Connection Management
Connection States
const channel = supabase . channel ( 'my-channel' )
channel . subscribe (( status , err ) => {
switch ( status ) {
case 'SUBSCRIBED' :
console . log ( 'Connected and subscribed' )
break
case 'TIMED_OUT' :
console . log ( 'Connection timed out' )
break
case 'CLOSED' :
console . log ( 'Connection closed' )
break
case 'CHANNEL_ERROR' :
console . log ( 'Channel error:' , err )
break
}
})
Reconnection
The client automatically handles reconnection:
const supabase = createClient ( url , key , {
realtime: {
params: {
eventsPerSecond: 10 // Rate limit
}
}
})
Rate Limiting
Realtime has built-in rate limiting:
Free tier : 200 concurrent connections, 10 events per second per channel
Pro tier : 500 concurrent connections, 100 events per second per channel
Enterprise : Custom limits
Best Practices
Always clean up subscriptions to prevent memory leaks: useEffect (() => {
const channel = supabase . channel ( 'my-channel' ). subscribe ()
return () => {
supabase . removeChannel ( channel )
}
}, [])
Use filters to reduce traffic
Filter changes at the database level to reduce unnecessary data: filter : 'user_id=eq.' + userId
Handle connection errors gracefully: channel . subscribe (( status , err ) => {
if ( status === 'CHANNEL_ERROR' ) {
console . error ( 'Channel error:' , err )
}
})
Use presence for online status
Presence automatically handles disconnections and is ideal for online/offline status.
Limits and Quotas
Connection Limits
Plan Max Concurrent Connections Events/Second per Channel Free 200 10 Pro 500 100 Enterprise Custom Custom
Message Size
Maximum payload size: 256 KB
Maximum channel name length: 255 characters
Next Steps
Channels Learn about channel management
Subscriptions Master subscription patterns