When subscribing to a Postgres changes, if multiple channels are subscribed to a given filtered change, only one of them gets the message
Bug report
- [x] I confirm this is a bug with Supabase, not with my own application.
- [x] I confirm I have searched the Docs, GitHub Discussions, and Discord.
Describe the bug
We've been encountering a lot of inconsistent race condition-like behavior around Realtime Postgres changes, and after digging through the websockets messages for a while I think I've found out the reason: When multiple subscriptions with different channel names should both receive a message for the same table row, only the most recently created subscription does.
This seems to only be an issue in the hosted environments. In local dev, every channel gets the message, as expected.
To Reproduce
- Set up multiple Realtime Postgres changes subscriptions to the same table, with different channel names and different but overlapping filters (i.e.
column_a=in.(A,B,C),column_a=in.(B,C,D),column_a=in.(D,E,F)). - Make a change in that table that should trigger multiple subscriptions (i.e., update a row that has
column_aset toD). - See that on the hosted environments, only one channel gets the websocket update message.
Expected behavior
All channels with subscriptions get the message.
Screenshots
Local dev:
Hosted Supabase environment:
This is with identical table changes in the same version of the web app running both places.
System information
- OS: n/a
- Browser (if applies): Chrome 140.0.7339.80
- Version of supabase-js: 2.56.0
- Version of Node.js: 22.x (Vercel)
Can you check which version of realtime-js is being used? I'm using the latest in the example below: 2.15.5
I've just created this deno script to try to replicate the issue but I always get events on both channels as expected.
It's setting up two channels with overlapping filters (assuming I understood well what you described). I then update a row with name=blue
import { RealtimeClient } from "npm:@supabase/[email protected]";
const http_url = "wss://REDACTED.supabase.co/realtime/v1";
const api_key = "REDACTED"
const client = new RealtimeClient(http_url, { params: { apikey: api_key } });
const channel1 = client.channel("channel1")
const channel2 = client.channel("channel2")
channel1.on('postgres_changes', {
event: '*',
schema: 'public',
table: 'test',
filter: 'name=in.(red, blue, brown)',
}, (payload) => {
console.log('Change arrived on channel1: ', payload)
})
channel2.on('postgres_changes', {
event: '*',
schema: 'public',
table: 'test',
filter: 'name=in.(black, blue, yellow)',
}, (payload) => {
console.log('Change arrived on channel2: ', payload)
})
channel1.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
console.log('Ready to receive database changes! for filter name=in.(red, blue, brown)')
}
console.log(status)
})
channel2.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
console.log('Ready to receive database changes! for filter name=in.(black, blue, yellow)')
}
console.log(status)
})
console.log("Waiting")
This is the output:
❯ deno run --allow-read --allow-net --allow-env=WS_NO_BUFFER_UTIL issue-1524.ts
Waiting
Ready to receive database changes! for filter name=in.(red, blue, brown)
SUBSCRIBED
Ready to receive database changes! for filter name=in.(black, blue, yellow)
SUBSCRIBED
Change arrived on channel1: {
schema: "public",
table: "test",
commit_timestamp: "2025-09-10T04:13:31.320Z",
eventType: "UPDATE",
new: {
created_at: "2025-09-10T04:07:24.369955+00:00",
id: 2,
name: "blue",
text: "Blue text has been updated"
},
old: { id: 2 },
errors: null
}
Change arrived on channel2: {
schema: "public",
table: "test",
commit_timestamp: "2025-09-10T04:13:31.320Z",
eventType: "UPDATE",
new: {
created_at: "2025-09-10T04:07:24.369955+00:00",
id: 2,
name: "blue",
text: "Blue text has been updated"
},
old: { id: 2 },
errors: null
}
Hi Eduardo – Boris here, I work on the same team as @icopp. Our realtime-js version is 2.15.1, according to node_modules/@supabase/supabase-js/package.json.
Also possibly worth noting (though likely irrelevant) is that we don't use RealtimeClient directly as you've done in your example, but rather only through @supabase/ssr's createBrowserClient; i.e.:
const supabase = createBrowserClient(/* some params */);
supabase.channel(/* unique id here */).on(REALTIME_LISTEN_TYPES.POSTGRES_CHANGES, /* options here*/);
Yeah the supabase client just uses the realtime client under the hood I don't see how this could happen.
Hmm the version used is not too old so probably not the issue.
Would you be able to run this deno script against your project with the appropriate table/filters and see if it can be replicated? I'm trying to find a reproducible code so that we can identify the actual issue.