Postgres Changes not firing
Your postgres_changes listener never receives inserts, updates or deletes. Check the supabase_realtime publication, RLS select policies, the session token and your filter syntax.
What you see
The channel subscribes, you insert a row in Studio or from your app, and the callback never runs:
supabase
.channel('booking-updates')
.on(
'postgres_changes',
{ event: 'INSERT', schema: 'public', table: 'bookings' },
(payload) => console.log('new booking', payload.new)
)
.subscribe((status, err) => console.log(status, err))
// SUBSCRIBED ... but no 'new booking'
Why it happens
Postgres Changes reads the database's write-ahead log through a publication called supabase_realtime. Realtime only sees tables that are in that publication. Then, for every change, it checks whether each subscriber is allowed to read the row under Row Level Security. If the table isn't published, or the subscriber's role has no select policy, the change is dropped without an error.
How to fix it
Add the table to the publication. Check what's published:
select schemaname, tablename from pg_publication_tables where pubname = 'supabase_realtime';If
bookingsis missing, add it:alter publication supabase_realtime add table public.bookings;You can also toggle tables on Studio's Database → Publications page.
Give the subscriber a
selectpolicy. With RLS on (it should be), Realtime evaluates the policy as the user's role. A signed-in user needs a policy forauthenticated:create policy "Guests see their own bookings" on public.bookings for select to authenticated using ((select auth.uid()) = guest_id);A client that isn't signed in uses the
anonrole and needs ananonpolicy to receive anything.Sign in before subscribing. supabase-js passes the session's access token to Realtime. If you subscribe before the session loads, the join happens as
anon. Subscribe aftergetSession()resolves, or insideonAuthStateChange.Check names and filter syntax.
schemaandtableare case sensitive. Filters use PostgREST-style operators as a string:{ event: 'UPDATE', schema: 'public', table: 'bookings', filter: 'room_id=eq.12' }Delete events can't be filtered, so a
filteronDELETEdrops everything.Want the old row on updates and deletes? Set the replica identity:
alter table public.bookings replica identity full;RLS isn't applied to delete events, so with RLS enabled a delete only carries the primary key.
Very large rows. When a change is bigger than 1 MB, the
newandoldpayloads only include fields of 64 bytes or less.
On SupaHost
Each project has its own database and its own Realtime tenant on your instance, so publish the table in the project you're subscribing to. SupaHost polls for changes every 100 ms with a 1 MB record limit. There's no dashboard toggle for Postgres Changes; use SQL or Studio. You can run the SQL above in Studio's SQL editor (Dashboard → your project → Database tab → Open Studio) or have an AI agent run it over MCP.
If you need changes delivered to many users at once, consider broadcasting from the database, which checks authorization once per join instead of once per change per subscriber.