Overview
Channels, subscriptions, broadcast, and presence.
The Realtime surface lets a Ruby process listen for Postgres changes, send broadcasts between connected clients, and track presence on a topic. Channels live on a single shared WebSocket; you open them with supabase.channel, attach listeners with channel.on_*, then call channel.subscribe to start the join handshake. Teardown is channel.unsubscribe (per-channel) or supabase.remove_channel / supabase.remove_all_channels (registry-level).
require "supabase"
supabase = Supabase.create_client(
supabase_url: ENV.fetch("SUPABASE_URL"),
supabase_key: ENV.fetch("SUPABASE_ANON_KEY")
)
channel = supabase
.channel("public:countries")
.on_postgres_changes("INSERT", schema: "public", table: "countries") do |payload|
puts "new country: #{payload['data']['record']}"
end
.subscribe do |state, error|
puts "subscribe state: #{state}"
end
# ...later...
supabase.remove_channel(channel) # or: channel.unsubscribeMethods on the top-level client
| Method | Description |
|---|---|
channel | Open (or re-open) a topic. Returns a Supabase::Realtime::Channel. |
remove_channel | Unsubscribe one channel and drop it from the registry. Closes the socket when the registry empties. |
remove_all_channels | Unsubscribe every tracked channel and close the socket. |
get_channels | Snapshot (Array<Channel>) of every channel currently tracked. Returns a dup so iteration is safe. |
Methods on a channel
| Method | Description |
|---|---|
on_postgres_changes | Listen to INSERT/UPDATE/DELETE (or "*") for a schema/table/filter. |
on_broadcast | Listen to a named broadcast event sent by another client on this topic. |
on_presence_sync / on_presence_join / on_presence_leave | Mirror Phoenix Presence state changes into your code. |
subscribe | Start the join handshake. Optional block fires with SUBSCRIBED / CHANNEL_ERROR / TIMED_OUT. |
unsubscribe | Send a phx_leave. State stays in LEAVING until the server acks. |
send_broadcast(event, payload) | Emit a broadcast frame to every other subscriber of the topic. |
track(payload) / untrack | Mutate this client's presence entry. See the on page for end-to-end examples. |
push_event(event, payload, timeout:) | Public low-level Phoenix push for custom events. Returns the Push so you can attach receive(...) blocks. |
presence_state | Snapshot of the current { key => [presences] } map. |
on_close(&block) / on_error(&block) | Channel-level lifecycle hooks (Ruby-only). |
Every realtime method is synchronous
Heartbeat, read-loop, and reconnect run on background Threads, and thread-safety is provided by explicit mutexes (Presence, Push, Timer, send-buffer). Listener blocks (on_postgres_changes, on_broadcast, presence callbacks, subscribe's status block) run on the read-thread — a slow callback delays delivery of the next frame. Exceptions raised inside a callback are caught by Supabase::Realtime::CallbackSafety and logged so the read-loop survives.
Sockets, reconnect, and on_reconnect_failed
Supabase::Realtime::Client owns one Socket. The production transport is Sockets::WebsocketClientSimple (constructed lazily when no transport: is injected). When the socket drops, a background thread reconnects with exponential backoff — initial_backoff * 2^(attempts - 1) seconds, capped at 60s, up to max_retries attempts. When all attempts are exhausted, the registered on_reconnect_failed callback fires with the last underlying error.
supabase.realtime.on_reconnect_failed do |error|
Rails.logger.error("realtime gave up after retries: #{error&.message}")
endThis callback fires once the client has given up after exhausting all retries.
Subscribe states
The block passed to subscribe is invoked with one of:
| State | Meaning |
|---|---|
"SUBSCRIBED" | The server acked the join. Listeners are now live. |
"CHANNEL_ERROR" | The server replied with an error (e.g. RLS rejected the join, or on_postgres_changes bindings didn't round-trip). |
"TIMED_OUT" | No reply within Types::DEFAULT_TIMEOUT_SECONDS (10s). |
"CLOSED" | Server-side phx_close after a successful join. |
These come from Supabase::Realtime::Types::SubscribeStates.