Skip to main content

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')
name
string
required
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()
event
string
required
The event type: INSERT, UPDATE, DELETE, or * for all.
schema
string
required
The database schema (usually public).
table
string
required
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"
}
eventType
string
The type of change: INSERT, UPDATE, or DELETE.
new
object
The new row data (for INSERT and UPDATE).
old
object
The old row data (for UPDATE and DELETE).
commit_timestamp
string
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
string
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 }
})
event
string
required
The event name for the broadcast.
payload
object
required
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()
})
state
object
required
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

sync
event
Fired when presence state is synchronized.
join
event
Fired when a new client joins the channel.
leave
event
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)
  }
}, [])
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)
  }
})
Presence automatically handles disconnections and is ideal for online/offline status.

Limits and Quotas

Connection Limits

PlanMax Concurrent ConnectionsEvents/Second per Channel
Free20010
Pro500100
EnterpriseCustomCustom

Message Size

  • Maximum payload size: 256 KB
  • Maximum channel name length: 255 characters

Next Steps

Channels

Learn about channel management

Subscriptions

Master subscription patterns