# 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.

Source: https://supahost.app/kb/realtime-too-many-channels · Updated 2026-11-11

## What you see

New channels stop joining and the subscribe callback reports an error whose reason mentions too many channels:

```text
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

1. **Remove channels when you're done.** In React, return a cleanup from the effect:

   ```jsx
   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.

2. **Count what's open.** While debugging, log the channels the client holds:

   ```js
   console.log(supabase.getChannels().map((c) => c.topic))
   ```

   The same topic listed many times is a leak.

3. **Use one channel for several events.** A channel can carry many event types and listeners. Instead of one channel per message type, chain them:

   ```js
   supabase
     .channel(`chat-room:${roomId}`)
     .on('broadcast', { event: 'message' }, onMessage)
     .on('broadcast', { event: 'typing' }, onTyping)
     .on('presence', { event: 'sync' }, onPresenceSync)
     .subscribe()
   ```

4. **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](/docs/realtime/broadcast).

5. **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](/docs/realtime/limits).

## Related

- [Realtime capacity and limits](/docs/realtime/limits)
- [Realtime TIMED_OUT and CHANNEL_ERROR](/kb/realtime-timed-out)
- [Broadcast](/docs/realtime/broadcast)
- [Use SupaHost with React (Vite)](/docs/getting-started/react)
