Short answer
Check that the table has a select policy: with RLS on and no policy, the API returns no data to the browser key. Then check who the request runs as: a signed-out user or an expired session matches no rows in a policy based on auth.uid(). If you get error 42501 instead of an empty array, the role is missing a grant.
Why the query returns no rows, and the fix
01
RLS is on and there is no select policy
“Once RLS is enabled, no data is accessible through the API when using a publishable key, until you create policies.” Supabase, Row Level Security
Turning on row level security without adding policies blocks every row for the browser key. Add a select policy that matches who should read the data, such as each user reading their own rows.
create policy "Users can view their own profile." on profiles for select to authenticated using ( (select auth.uid()) = user_id );02
The request runs signed out
“This means that a policy like: USING (auth.uid() = user_id) will silently fail for unauthenticated users” Supabase, Row Level Security
If the query runs before the session loads, or after it expires, auth.uid() is null and a policy comparing it to user_id never matches. Supabase recommends checking for a signed-in user explicitly in the policy so the intent is clear.
03
The rows don’t match the policy
“Think of a policy as adding a WHERE clause to every query.” Supabase, Row Level Security
A policy works like a filter added to every query. Rows inserted without a user_id, or with another user’s ID, never come back for this user. In our experience this often happens with rows created by a seed script or a server using the service role key.
04
An error, not an empty array, means a missing grant
“A missing grant raises a 42501 error before any policy runs.” Supabase, Row Level Security
If the query fails with 42501 instead of returning an empty array, the role lacks permission on the table itself, before any policy runs. Supabase says to check grants before changing the policy.
Checked against the vendor’s docs on : Supabase docs: Row Level Security.
Rather hand it over?
Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.
- from $450App Fix TicketOne reproducible bug in an existing mobile or web app, diagnosed and fixed.See scope and start
- from $450AI-Built App RescueA Lovable, Bolt or Cursor app checked end to end in 3 days, then its worst problems fixed at a fixed price.No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way.See scope and start
Related: Supabase PGRST116 from .single() · RLS is on: is the data safe now? · Supabase OAuth provider disabled · Supabase Invalid login credentials