supabase-rb-rb
Realtime

Unsubscribe from a channel

Send phx_leave for a Realtime channel and wait for the server's ack.

Tear down a single channel's subscription. unsubscribe flips the channel state to LEAVING, emits a phx_leave push to the server, and stays in LEAVING until the server acks (or errors / times out). Once the ack arrives, the channel goes to CLOSED, every registered on_close hook fires, and the channel is removed from client.channels so it stops receiving dispatched frames.

To also close the underlying socket when the registry empties, use remove_channel instead — unsubscribe only tears down this one channel.

Signature

channel.unsubscribe

Parameters

This method has no parameters.

Returns

Returns
self (Supabase::Realtime::Channel)

Returns the channel. The leave is in flight when this method returns; the channel is still in LEAVING until the server replies. Idempotent — calling unsubscribe on an already-CLOSED channel is a no-op.

Example — unsubscribe a single channel

channel = supabase.channel("public:countries").on_postgres_changes("*", schema: "public", table: "countries") { |p| handle(p) }.subscribe

# ...some work...

channel.unsubscribe

Example — wait for CLOSED before exiting

unsubscribe returns before the server acks. Hook on_close to know when the leave is fully done — useful in test teardown or short-lived scripts.

done = Queue.new
channel.on_close { done.push(:closed) }
channel.unsubscribe
done.pop  # blocks until phx_leave is acked (or times out → on_close still fires via on_leave_ack)

Example — re-subscribing requires a fresh channel

subscribe may only be called once per channel object. To re-subscribe to the same topic after an unsubscribe, open a new channel.

channel.unsubscribe

# A new channel for the same topic.
channel = supabase
  .channel("public:countries")
  .on_postgres_changes("*", schema: "public", table: "countries") { |p| handle(p) }
  .subscribe

Lifecycle

  • Pre-condition: channel is JOINED (or JOINING). On CLOSED / LEAVING, unsubscribe is a no-op.
  • State flips to LEAVING. The phx_leave push is registered in pending_pushes with a 10s timeout.
  • Server replies with phx_replyon_leave_ack → state to CLOSED, on_close hooks fire, rejoin timer reset.
  • Server replies with phx_reply (error) or no reply within 10s → still on_leave_ack (same handler for all three statuses), so the channel cleans up locally regardless of server response.
  • After CLOSED, the channel is removed from client.channels via Client#_remove_channel. It's no longer dispatched to.

Synchronous return; server ack arrives later

unsubscribe is synchronous — it returns once the phx_leave frame is queued (or sent, if connected). The actual server ack arrives asynchronously on the read-thread, which is why on_close is the only reliable way to detect "leave fully complete". For non-blocking teardown of multiple channels, use supabase.remove_channel(channel) from the top-level client under async: true — it returns an Async::Task you can .wait on (see remove_channel).

On this page