You run supabase.from("posts").select("*") and get { data: [], error: null }. The rows are in the table. The same query works in the SQL editor. Your app shows nothing.
That's Row Level Security doing its job. RLS on with no matching SELECT policy filters every row out: zero rows, no error. The SQL editor runs as postgres, which owns the tables and skips RLS, so it sees everything. Your app goes through the Data API as anon or authenticated, and those roles only see rows a policy allows.
If you get 42501 permission denied for table instead, that's a missing GRANT, not this bug: the Oct 30 grants post.
1. RLS on, no SELECT policy
From the Supabase RLS docs: "Once RLS is enabled, no data is accessible through the API when using a publishable key, until you create policies."
create policy "anyone can read posts"
on public.posts
for select
using (true);
2. Policy targets the wrong role
to authenticated never matches a signed-out visitor, or a signed-in one whose session hasn't loaded yet. For public reads use to anon, authenticated, or leave out to.
3. auth.uid() is null
using (auth.uid() = user_id) only works when the request carries the user's JWT. Without it, null = user_id is never true, so every row disappears. Usual reasons: not signed in, querying on first render before the session is ready, or server code creating a client with only the anon key.
const { data: { session } } = await supabase.auth.getSession();
console.log("uid:", session?.user?.id); // undefined = no session
4. The rows belong to someone else
The policy is right; the data isn't. Check in the SQL editor:
select id, user_id from posts;
user_id null or another account's id means the insert path is wrong, not the policy.
5. Policy checks the wrong column
AI-generated schemas sometimes call the owner column user_id, owner_id, or reuse id. A policy comparing auth.uid() to the row's primary key matches nothing.
Confirm it in two minutes
select polname, cmd, roles, qual
from pg_policies
where tablename = 'posts';
No row with cmd = SELECT? That's cause 1. Then log user?.id right before the query. If it's undefined, fix auth first.
For owner-only data, this is the shape the Supabase docs use:
create policy "owners can read their posts"
on public.posts
for select
to authenticated
using ( (select auth.uid()) = user_id );
Don't fix it by disabling RLS. That hands every row to anyone holding your publishable key.
Full guide
Longer version with the debug policy trick, the empty-array vs 42501 difference, and an FAQ:
https://fixloop.dev/guides/supabase-select-returns-empty-array
I run FixLoop. Still stuck? Paste the error and we'll email you the likely cause, free.
Top comments (0)