Subscribe to Supabase Realtime: A Practical Guide to WebSocket Table Changes
Learn how to subscribe to Supabase Realtime via WebSocket, configure RLS and permissions, and avoid common pitfalls. Includes a live code example, limits, and verification steps.
17 May 2026, 09:52 UTC

Why Realtime Matters
When you need your front‑end to react instantly to database changes, Supabase Realtime gives you a WebSocket channel that streams Postgres logical‑decoding events. The core takeaway: you can subscribe to a single table, receive INSERT, UPDATE, and DELETE payloads, and let the client automatically reconnect on network hiccups. Below you’ll find a step‑by‑step configuration, a live example, and the limits and common pitfalls to watch for.
Getting Started: Enable Logical Decoding and RLS
Supabase projects already ship with the rds.logical_decoding extension enabled. What you must do is make sure the table you want to listen to has Row‑Level Security (RLS) enabled and that the role you’ll use from the client has USAGE on the schema.
# In Supabase SQL editor
ALTER TABLE public.my_table ENABLE ROW LEVEL SECURITY;
GRANT USAGE ON SCHEMA public TO anon;
Without RLS, the Realtime engine will silently drop events for that table. Granting USAGE is a minimal requirement; you can also grant SELECT if you want the client to read the data directly.
Subscribing from the Client
Use the @supabase/supabase-js client. The from() method takes a table name and returns a RealtimeChannel. The on() method registers a callback for all events. You can filter to a specific event type if you prefer.
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = 'https://xyzcompany.supabase.co'
const supabaseKey = 'public-anon-key'
const supabase = createClient(supabaseUrl, supabaseKey)
// Subscribe to all changes on my_table
const myTableChannel = supabase
.from('my_table')
.on('*', payload => {
console.log('Realtime event:', payload)
})
.subscribe()
// Optional: listen for connection status
supabase.on('postgres-realtime:connected', () => {
console.log('Realtime connected')
})
supabase.on('postgres-realtime:disconnected', () => {
console.log('Realtime disconnected – will auto‑reconnect')
})
Key points:
payload.eventTypewill be one ofINSERT,UPDATE, orDELETE.- For
INSERTandUPDATE,payload.newcontains the new row; forUPDATEandDELETE,payload.oldcontains the previous row. - The client automatically reconnects if the WebSocket drops; you only need to handle the optional status callbacks if you want UI feedback.
Filtering Events
To reduce traffic, you can filter by column values or event type. For example, only receive updates where status = 'active':
supabase
.from('my_table')
.on('UPDATE', payload => {
console.log('Active update:', payload)
})
.match({ status: 'active' })
.subscribe()
Limits You Should Know
| Limit | Value | Impact |
|---|---|---|
| Concurrent WebSocket connections | ~1,000 per project | High‑traffic apps should monitor via the dashboard. |
| Max payload size | ≈1 MB | Large rows can cause dropped frames; batch or filter changes. |
| Multi‑table subscriptions | Not supported | Use separate listeners for each table. |
| Logical decoding retention | 10 min default | Events older than this may be lost if the client is offline. |
Common Mistakes & How to Avoid Them
- RLS disabled: No events will fire. Verify by running
SELECT has_rls('public.my_table');in the SQL editor. - Missing USAGE: The client role must have
USAGEon the schema. Grant it withGRANT USAGE ON SCHEMA public TO anon;. - Large payloads: A single row with many columns can exceed the 1 MB limit. Use
.select('col1, col2')to trim or batch changes server‑side. - Connection saturation: If you have more than ~1,000 clients, you’ll hit the limit. Consider a message broker or scaling your Supabase instance.
- Assuming instant delivery: Realtime delivers events as soon as they are decoded, but network latency and reconnection delays can add a few hundred milliseconds.
How to Verify Your Setup
- In the Supabase SQL editor, create a test table with RLS enabled.
CREATE TABLE public.test_realtime ( id serial PRIMARY KEY, data text NOT NULL ); ALTER TABLE public.test_realtime ENABLE ROW LEVEL SECURITY; GRANT USAGE ON SCHEMA public TO anon; - Insert a row via the client or the SQL editor.
await supabase.from('test_realtime').insert([{ data: 'hello' }]) - Open the browser console and watch for the
Realtime eventlog. You should see anINSERTpayload withnewcontaining the inserted row. - Check the console for
postgres-realtime:connectedandpostgres-realtime:disconnectedevents to confirm auto‑reconnect behavior. - In DevTools’ Network tab, filter by
wsand inspect the frames. You should see a JSON payload for each change.
Practical Checklist
- RLS enabled on the target table.
- Client role has USAGE on the schema.
- Subscribe to the correct table and event types.
- Monitor connection count in the Supabase dashboard.
- Keep payloads small; filter columns if necessary.
- Handle
postgres-realtime:disconnectedgracefully in the UI.
Conclusion
Supabase Realtime turns Postgres changes into a real‑time API you can hook into with a few lines of JavaScript. By following the steps above and keeping an eye on the limits and common pitfalls, you can build responsive applications that stay in sync with your database without writing custom backend logic.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.