Supabase returns an empty array but the table has data? It’s usually RLS
You open the Supabase Table Editor and the rows are right there. Your app runs supabase.from('projects').select('*') and gets back data: [] and error: null. No error, no warning, just nothing.
This is one of the most common problems in vibe-coded apps, and it is almost always Row Level Security (RLS) doing exactly what it was told to do.
Why you get an empty array instead of an error
RLS works like an invisible WHERE clause added to every query. When RLS is enabled on a table, Postgres only returns rows that pass at least one policy for that operation and that role. If no policy exists, or none match, every row is filtered out. Filtering everything out isn’t an error; it’s a successful query with zero results. Supabase’s own troubleshooting guide puts it simply: usually RLS is enabled and there’s no policy, or you don’t meet it.
The Table Editor shows the rows because the dashboard uses a privileged connection that isn’t subject to your policies. Your app uses the publishable (anon) key, which is.
Writes behave differently. An insert that violates a policy’s with check raises an error (code 42501), but an update or delete whose using clause matches no rows quietly affects zero rows. “My update does nothing” is the same problem in disguise.
The four usual suspects
1. RLS is on, but there is no SELECT policy
AI tools often enable RLS (good) and then only write an insert policy, or none at all. In the dashboard, open Authentication → Policies and check whether the table has a policy for SELECT. Supabase’s Security Advisor flags tables with “RLS enabled, no policy” too.
2. The policy requires a logged-in user, and the request isn’t logged in
A typical policy looks like this:
create policy "Users read their own projects"
on public.projects for select
to authenticated
using ( (select auth.uid()) = user_id );
If the query runs before the session is restored (for example in a useEffect on first page load), or from a server route that creates its own client without the user’s token, the request arrives as anon. auth.uid() is then null, the condition is never true, and you get [].
A quick test: log (await supabase.auth.getSession()).data.session right before the query. If it’s null, the problem is your auth timing, not your policy.
3. The policy compares the wrong column
The policy checks user_id, but your rows were inserted with user_id empty, or the code stores the user’s email or a profile id instead of the auth user id. The rule is fine, the data never matches. Compare a row’s user_id with the id of the user in Authentication → Users.
4. It isn’t RLS at all
Before blaming policies, rule out the boring causes: a filter like .eq('status', 'Active') when the data says active, a query against the wrong project (preview vs production keys), or a table in a schema other than public.
Not sure which of these is your problem? The free health check looks at your live app in seconds, or request a free scan and we’ll tell you what we find.
Empty array vs. “permission denied”
There are two separate locks on every table, and they fail differently:
| Layer | Question it answers | When it says no |
|---|---|---|
| Grants | May this role touch the table at all? | Error 42501 permission denied for table … |
| RLS policies | Which rows may this role see or change? | Empty result, no error (reads) |
If you see 42501 on a read, you’re past the RLS question: the role has no grant. That’s about to become a lot more common, because from October 30, 2026 Supabase stops granting access to new tables automatically. Details in our guide to the Oct 30 change.
Whatever you do, don’t “fix” it by turning RLS off
Disabling RLS on the table is a valid ten-second test: if the rows suddenly appear, you know it’s a policy problem. As a fix it’s a disaster. With RLS off, anyone holding your publishable key, which is in every visitor’s browser, can read and often write the whole table. The same goes for a policy of using (true) on anything private, and for swapping the anon key for the service_role key in the frontend. All three make the empty array go away by giving everyone everything.
The short version
[]with no error = rows filtered out by RLS (or by your own filter)- Check that a
SELECTpolicy exists for the role your request actually uses - Confirm there’s a session at the moment the query runs
- Check that the column your policy compares holds the auth user id
42501means a missing grant, a different problem
Stuck? Get a free scan of your app
Send us your repo and a one-line description of what’s broken. We’ll email you the top issues we find and why they matter. If you’d rather not touch it yourself, the $199 Fix covers up to 5 issues, delivered as a pull request with tests — not fixed, full refund.
FAQ
Why does my Supabase query return [] with no error?
Row Level Security filtered every row out. With RLS on, Postgres only returns rows that pass a policy for that operation and role, and filtering everything out is a successful query with zero results.
Why can I see the rows in the Supabase Table Editor?
The dashboard uses a privileged connection that isn’t subject to your policies. Your app uses the publishable (anon) key, which is.
What is the difference between an empty array and error 42501?
An empty array means RLS policies filtered the rows. 42501 “permission denied” means the role has no grant on the table at all, which is a different problem.
Should I just turn RLS off?
Only as a ten-second test. As a fix it lets anyone with your publishable key, which is in every visitor’s browser, read and often write the whole table.
Related guides
- Is your Lovable app leaking user data? How to tell if your Supabase RLS is broken
- Supabase’s October 30 change is live: a 5-minute checklist for new tables
- Your Lovable app freezes or goes blank right after login: here’s why
Launching soon? Run through our launch checklist for AI-built apps: 14 checks, each linked to the full guide.