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

Source: https://supahost.app/kb/realtime-postgres-changes · Updated 2026-11-11

## What you see

The channel subscribes, you insert a row in Studio or from your app, and the callback never runs:

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

1. **Add the table to the publication.** Check what's published:

   ```sql
   select schemaname, tablename
   from pg_publication_tables
   where pubname = 'supabase_realtime';
   ```

   If `bookings` is missing, add it:

   ```sql
   alter publication supabase_realtime add table public.bookings;
   ```

   You can also toggle tables on Studio's **Database → Publications** page.

2. **Give the subscriber a `select` policy.** With RLS on (it should be), Realtime evaluates the policy as the user's role. A signed-in user needs a policy for `authenticated`:

   ```sql
   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 `anon` role and needs an `anon` policy to receive anything.

3. **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 after `getSession()` resolves, or inside `onAuthStateChange`.

4. **Check names and filter syntax.** `schema` and `table` are case sensitive. Filters use PostgREST-style operators as a string:

   ```js
   { event: 'UPDATE', schema: 'public', table: 'bookings', filter: 'room_id=eq.12' }
   ```

   Delete events can't be filtered, so a `filter` on `DELETE` drops everything.

5. **Want the old row on updates and deletes?** Set the replica identity:

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

6. **Very large rows.** When a change is bigger than 1 MB, the `new` and `old` payloads 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](/kb/realtime-broadcast-from-database), which checks authorization once per join instead of once per change per subscriber.

## Related

- [Postgres Changes](/docs/realtime/postgres-changes)
- [Row Level Security](/docs/database/row-level-security)
- [Broadcasting from the database](/kb/realtime-broadcast-from-database)
- [Realtime messages not arriving](/kb/realtime-messages-not-arriving)
