supabase realtime "channel error: subscription failed" RLS policy fix
Fixes Supabase Realtime subscription failures caused by Row Level Security. Use when the channel joins then errors, when realtime worked before RLS was enabled, and when the anon or authenticated role lacks a SELECT policy. Not for network or websocket errors, which fail differently.
TL;DR
Realtime replays table changes through Row Level Security, so a table with RLS enabled and no SELECT policy for your client's role rejects the subscription. Add the policy, add the table to the supabase_realtime publication, and resubscribe.
Error
channel error: subscription failed - realtime subscription rejected (RLS policy check)Steps
- Confirm the channel setup matches the table exactly: table name, schema, and any
postgres_changesfilter. A typo here fails the same way.
Expected output: you rule out naming mistakes before touching policies.
- Add a SELECT policy for the role your client uses (
anonfor public clients,authenticatedfor logged-in users):
CREATE POLICY "public read" ON [your_table]
FOR SELECT TO anon USING (true);Expected output: the role can read the table, which is what the realtime subscription checks.
- Add the table to the realtime publication:
ALTER PUBLICATION supabase_realtime ADD TABLE [your_table];Expected output: ALTER PUBLICATION succeeds. Tables not in this publication never emit realtime events.
- Resubscribe and check the channel status reaches joined/subscribed instead of erroring.
Expected output: inserts on the table now arrive on the channel.
When to use
- The channel errors right after joining
- Realtime worked before someone enabled RLS on the table
- You are using the anon key or a logged-in user and reads work via the API but not via realtime
When not to use
- Websocket connection failures or timeouts (thats network, not RLS)
- Events arrive but with stale data (thats a publication or filter issue)
- You want realtime without any read access at all (RLS still applies; there is no bypass for subscriptions)
Variant phrasings
postgres_changes filter gets no events
The filter (event type, schema, table, column filter) must match the change exactly. An overly narrow filter looks identical to a broken subscription.
realtime works for postgres public schema tables but not private ones
Private-schema tables need both the RLS policy and the publication entry; the dashboard toggle only covers public.
Why it happens
Supabase Realtime does not bypass RLS: each change is re-checked against the subscribing role's policies before delivery. Enabling RLS without a matching SELECT policy turns every subscription into a rejection, which the client surfaces as a channel error.
Edge cases
- Policies using
auth.uid()only work for theauthenticatedrole; the anon key never satisfies them. - RLS enabled with zero policies denies everything, which is the most common way this breaks right after "securing" a table.
- Updates and deletes also need the table's replica identity set, or realtime cannot deliver the old row.
Provenance
Resolved from the public thread: https://vectle.com/posts/pstpj7dF4CUUrKfsPbj47eew