TooManyChannels error
A Realtime join fails because one client has too many open channels. Why channels leak in React and other frameworks, and how to clean them up and reuse them. Limit is 100 per client on SupaHost.
What you see
New channels stop joining and the subscribe callback reports an error whose reason mentions too many channels:
CHANNEL_ERROR Error: ChannelRateLimitReached: Too many channels
In the WebSocket frames (DevTools → Network → WS) you may see the join reply rejected with too_many_channels. It usually starts after the app has been open for a while, or after navigating between pages several times.
Why it happens
Realtime caps how many channels a single client connection can join. Every supabase.channel(...).subscribe() adds one, and it stays joined until you remove it. The common cause is a leak: a component creates a channel each time it mounts or re-renders and never removes the old one. Unique topic names per render (for example a topic with a timestamp) make it worse, because nothing gets reused.
How to fix it
Remove channels when you're done. In React, return a cleanup from the effect:
useEffect(() => { const channel = supabase .channel(`chat-room:${roomId}`) .on('broadcast', { event: 'message' }, ({ payload }) => addMessage(payload)) .subscribe() return () => { supabase.removeChannel(channel) } }, [roomId])The dependency array matters: without it the effect runs on every render. React's Strict Mode mounts effects twice in development, which is fine as long as the cleanup runs.
Count what's open. While debugging, log the channels the client holds:
console.log(supabase.getChannels().map((c) => c.topic))The same topic listed many times is a leak.
Use one channel for several events. A channel can carry many event types and listeners. Instead of one channel per message type, chain them:
supabase .channel(`chat-room:${roomId}`) .on('broadcast', { event: 'message' }, onMessage) .on('broadcast', { event: 'typing' }, onTyping) .on('presence', { event: 'sync' }, onPresenceSync) .subscribe()Don't subscribe just to send from a server. A backend that only publishes can use the Broadcast REST endpoint (
POST /realtime/v1/api/broadcast) and never join a channel. See Broadcast.Remove everything on sign-out. Call
supabase.removeAllChannels()when a user logs out, so the next user starts clean.
On SupaHost
Each project's Realtime tenant allows 100 channels per client (max_channels_per_client). That's per connection, not per project: 100 users each with a few channels is fine. If you genuinely need more than 100 topics per user, rethink the design, for example one channel per conversation list rather than one per conversation. The other per-project limits are listed in Realtime capacity and limits.