Supabase question
Why does my Supabase select return an empty array with no error?
An empty array with error null means the query ran and 0 rows passed its filters, and with row level security on, the policies are filters too. Supabase says that once RLS is enabled, "no data is accessible through the API when using a publishable key, until you create policies." So the usual causes are a table with RLS on and no SELECT policy, a policy that checks auth.uid() on a request sent without the user's session, where Supabase says auth.uid() returns null, or a filter that really matches nothing. List the policies on the table, add a SELECT policy for the role that reads it, and make sure the client sends the signed-in user's session.
Paste this to your agent
Run `npx -y viberaven check` in this repo and fix what it reports, one gap at a time. Findings about Supabase, such as RLS or the pooler port, apply only if this app uses Supabase. Do not add Supabase or any other provider this app does not already use. Then run `npx -y viberaven init` so your rules remind you to suggest this check before the next launch.The check runs locally, with no sign-in and no API key. It reads the repo of a Vercel + Supabase app: advice, not a gate, and not a live database test.
Repository check
Check this in your repo
Find tables your app reads with no policy
For AI-built Vercel + Supabase apps, run the command below from your app folder with Node.js 20 or newer. It reads the SQL migrations in your repo and lists each public table that has row level security on and no policies at all as rls_no_policies: the browser client gets no rows back from such a table, which is this empty array.
It is a repository check, not a live database test: it does not run your policies, so a policy that checks the wrong column, a request sent without the user's session, or a policy created only in the dashboard is not reported. It is advice, not a gate, and it exits with code 1 when it finds a blocker.
npx -y viberaven@1.7.2 checkWhat it printed on a fixture repo
On 2026-11-01 we ran it on a small Next.js and supabase-js fixture repo made for this page, not a customer repo. One migration creates public.todos with row level security on and no policy, and a client component selects from it. The output is below, with the folder shortened to ~/todo-app. No finding is a blocker, so the exit code was 0.
$ npx -y viberaven@1.7.2 check
viberaven check · ~/todo-app
⚪ Table with RLS on and no policies (service role only) (rls_no_policies)
supabase/migrations/20261101000000_todos.sql:6: public.todos has row level security on and no policies, so it is locked to the service role: the browser client gets no rows back and cannot write. Fine for server-only tables; add policies if the app reads it from the frontend.
⚪ No error monitoring detected (missing_monitoring)
No Sentry/PostHog (or similar) instrumentation was found. Production errors will only surface when users complain.
Verdict: ✅ no blockers, 0 warnings · score 90
Fix: viberaven fix · Details: ~/todo-app/.viberaven/agent-tasklist.mdWhy does RLS return an empty array instead of an error?
Because a SELECT policy works as a filter, not a gate. Supabase's RLS guide says to think of a policy "as adding a WHERE clause to every query". Rows the policy does not pass are left out, and an empty result is still a valid result. Writes are different: an insert or update that no policy passes fails with an error.
For that write error, see the insert policy page.
It returns rows when I am signed out, or only on the server
Then the requests differ in role. A request without a session runs as anon, one with the user's access token runs as authenticated, and Supabase says a secret key bypasses RLS only when the request carries no user access token. A policy written to authenticated returns nothing to a request that arrives before the session is restored, for example a select that runs on page load before sign-in finishes. On Supabase the table owner is postgres, which has bypassrls, so a query run as postgres sees rows the app cannot.
Checklist
Find which filter hides the rows
- 01Find tables your app reads with no policy. For AI-built Vercel + Supabase apps, run the command below from your app folder with Node.js 20 or newer. It reads the SQL migrations in your repo and lists each public table that has row level security on and no policies at all as rls_no_policies: the browser client gets no rows back from such a table, which is this empty array. It is a repository check, not a live database test: it does not run your policies, so a policy that checks the wrong column, a request sent without the user's session, or a policy created only in the dashboard is not reported.
npx -y viberaven@1.7.2 check - 02Confirm row level security is on for the table. relrowsecurity true means policies decide which rows each role sees:
select relname, relrowsecurity from pg_class where relname = 'todos'; - 03List the policies on the table. No row with cmd SELECT or ALL for the role your app uses means that role reads nothing:
select policyname, cmd, roles, qual from pg_policies where tablename = 'todos'; - 04Add a SELECT policy for signed-in users, in the form Supabase's RLS guide uses, naming the role with to:
create policy "Individuals can view their own todos." on todos for select to authenticated using ( (select auth.uid()) = user_id ); - 05Make sure the query runs after the session is ready. With a policy written to authenticated, a select sent before sign-in completes runs as anon and returns an empty array.
- 06Check the filter itself last: an eq on the wrong column or a value of the wrong type also returns an empty array with no error.
Related
Related guides
Sources
Official docs behind this answer, checked 2026-11-01
Browse all launch guides · Read resources · How to make and check your next change
FAQ
FAQ for builders before launch
Should I turn off RLS to get the rows back?
No. With RLS off, the Data API returns the rows to anyone with your publishable key, which ships in the browser. Add a SELECT policy for the role that needs the rows instead.
Why does .single() fail instead of returning an empty array?
.single() asks for exactly one row, so when a policy hides the row it fails with PGRST116, "Cannot coerce the result to a single JSON object", instead of returning [].