Realtime on SupaHost

How Supabase Realtime works on SupaHost. Broadcast, Presence and Postgres Changes over WebSockets, per-project limits, capacity per plan, and no per-message or per-connection fees.

Supabase Realtime lets clients exchange messages and receive database changes over a WebSocket. Every SupaHost project has Realtime, with all three features of the open-source server: Broadcast, Presence and Postgres Changes. You use it through supabase.channel() in supabase-js (or the equivalent in other client libraries), with no changes to your code.

Realtime on SupaHost is included in your instance's flat monthly price. There are no fees per message, per concurrent connection or per channel. What limits you is the instance's capacity and a set of per-project rate limits, covered in Realtime capacity and limits.

The three features

Feature What it does Good for
Broadcast Sends low-latency messages from one client to every other client on a channel. Can also be sent from your server or the database Chat, cursors, live reactions, "someone is typing", game moves
Presence Keeps a shared list of who is on a channel, with a small state object per client Online indicators, "3 people viewing", avatars on a shared document
Postgres Changes Streams inserts, updates and deletes from your tables to subscribed clients, filtered by RLS Live dashboards, order status, small collaborative lists

All three run over the same connection. A client opens one WebSocket and joins as many channels as it needs (up to 100 per client).

How it's set up on SupaHost

  • Each instance runs one Realtime server, and each project is a separate tenant on it with its own limits. Projects can't see each other's channels.
  • Clients connect through your project URL. supabase-js derives the WebSocket address from the URL you pass to createClient:
wss://app-one.acme.supahost.space/realtime/v1/websocket
  • The REST endpoint for sending Broadcast messages without a WebSocket is under https://app-one.acme.supahost.space/realtime/v1/api/.
  • Like every other API on your project, Realtime needs your project's anon key (or a user's access token). supabase-js sends it for you.
  • Postgres Changes reads from each project's own database, so a busy table in one project doesn't feed changes into another.

A first channel

This connects to a channel for one board in a project management app, listens for "card-moved" messages, and sends one.

import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  process.env.SUPABASE_URL!, // https://app-one.acme.supahost.space
  process.env.SUPABASE_ANON_KEY!
)

const board = supabase.channel('board:42')

board
  .on('broadcast', { event: 'card-moved' }, ({ payload }) => {
    console.log(`Card ${payload.cardId} moved to ${payload.column}`)
  })
  .subscribe(async (status) => {
    if (status !== 'SUBSCRIBED') return
    await board.send({
      type: 'broadcast',
      event: 'card-moved',
      payload: { cardId: 'c-7', column: 'done' },
    })
  })

// Later, when the user leaves the board
await supabase.removeChannel(board)

The subscribe callback receives the channel status: SUBSCRIBED, TIMED_OUT, CHANNEL_ERROR or CLOSED. Handle at least the first three. If you see TIMED_OUT or CHANNEL_ERROR, see Realtime TIMED_OUT and CHANNEL_ERROR.

Public and private channels

By default a channel is public: anyone with your anon key who knows the channel name can join it and send messages. That's fine for things like a public live counter, but not for private data.

A channel created with { config: { private: true } } is checked against RLS policies on the realtime.messages table, so you decide which users can receive and send on which topics. Use private channels for anything user-specific. See Realtime authorization.

Postgres Changes has its own check: clients only receive rows they could read through your table's RLS policies.

Picking the right feature

  • To show other users what someone is doing right now (typing, cursor position, a game move), use Broadcast. Messages aren't stored; they go to whoever is connected.
  • To show who is online, use Presence. Realtime keeps the list in sync as clients join and leave.
  • To keep a view in sync with a table, start with Postgres Changes for low to moderate traffic. When many clients watch the same busy table, send changes with Broadcast from the database instead. It scales better because each change is sent once to a topic rather than checked against every subscriber. See Broadcasting from the database.

Realtime is not a durable message queue. If a client is offline when a message is sent, it misses it. Store anything that must not be lost in a table, and use Realtime to tell clients to refresh. For background work, see Queues with pgmq.

Capacity at a glance

These are capacity guides from SupaHost's load tests, for a whole instance (shared by its projects):

Plan Concurrent Realtime connections (guide)
Builder about 5,000
Pro about 15,000
Business about 30,000

Each project also has fixed rate limits, such as 5,000 events per second and 500 channel joins per second. Details and what happens when you reach them are in Realtime capacity and limits.

Realtime traffic goes through your instance's gateway, so messages sent to clients count toward your monthly egress.

Debugging

  • Open your browser's DevTools, go to the Network tab, filter by WS, and select the websocket connection to see every frame.
  • Log channel status changes in your subscribe callback.
  • SupaHost doesn't have a searchable log explorer in the dashboard, and Studio's Logs pages aren't available.

See Debugging Realtime with logs and DevTools and Realtime messages not arriving.

Next steps

Updated 11 November 2026 · View as Markdown · Something wrong or missing? Tell us