Create a channel
Open a Supabase Realtime channel on the shared WebSocket connection.
Open a channel on a topic. The call is cheap — it creates a Supabase::Realtime::Channel instance, registers it in client.channels, and returns it. No socket I/O happens until you attach listeners and call subscribe.
Signature
channel = supabase.channel(topic, params: nil)The same method lives on the realtime sub-client directly (supabase.realtime.channel(topic, params: nil)) — the top-level shortcut just forwards.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
topic | String | Required | Topic name. Auto-prefixed with realtime: when missing, so "public:users" and "realtime:public:users" reach the same channel. |
params | Hash | Optional | Join-payload override. Defaults to a config hash with broadcast { ack: false, self: false }, presence { key: "", enabled: false }, private: false. Pass your own to enable broadcast acks, presence keys, or private (RLS-gated) channels. |
Returns
A channel scoped to topic. Chain on_postgres_changes / on_broadcast / on_presence_*, then call subscribe to start the join handshake. The channel is added to client.get_channels immediately — the registry tracks intent, not connection state.
Example — open a channel and attach a Postgres-changes listener
channel = supabase
.channel("public:countries")
.on_postgres_changes("*", schema: "public", table: "countries") do |payload|
puts payload["data"]["type"], payload["data"]["record"]
end
.subscribeExample — auto-prefixing
Topic names get a realtime: prefix automatically. The two calls below open the same topic — pre-prefixed names are passed through unchanged.
supabase.channel("public:users") # → "realtime:public:users"
supabase.channel("realtime:public:users") # → "realtime:public:users"Example — broadcast acks and presence key
Override params to ask the server to ack broadcasts and to enable Presence with a stable key (the key groups all metas from this client under the same map entry).
channel = supabase.channel("room:42", params: {
"config" => {
"broadcast" => { "ack" => true, "self" => false },
"presence" => { "key" => "user-#{current_user.id}", "enabled" => true },
"private" => false
}
})Example — private channels (RLS-gated)
Pass config.private: true so the server enforces the topic's realtime.messages policies against the JWT carried in the join payload. The current access_token (from set_auth, or the anon key on initial create) is automatically placed at the root of the join payload — you don't need to add it to params.
private_channel = supabase.channel("room:tenant-42", params: {
"config" => {
"broadcast" => { "ack" => false, "self" => false },
"presence" => { "key" => "", "enabled" => false },
"private" => true
}
})Multiple channels on the same topic
The registry is a flat list — client.channel(topic) always returns a new instance, even when a channel for the same topic already exists. Each gets its own join_ref / subscription lifecycle. To look up an existing channel, walk supabase.get_channels.find { |c| c.topic == "realtime:public:users" }.
Synchronous surface
The entire channel surface is synchronous — channel returns a fully usable object, and every method you call on it blocks the calling thread for the duration of the socket write. Heartbeat / read-loop / reconnect run on background Threads; if you want non-blocking realtime calls from the top-level client, see remove_channel for the async: true Async::Task shape.