const { data, error } = await supabase.from('todos').select('*')
// data: [] error: null
The table has 500 rows, and you can see them in the Table Editor. The query in your app returns an empty array, and there's no error. This is the most common Supabase question by a wide margin, and the answer is almost always Row Level Security.
What's going on
Supabase exposes your Postgres tables through an auto-generated API. Requests from your frontend run as the Postgres role anon (not signed in) or authenticated (signed in), never as the table owner.
When Row Level Security (RLS) is enabled on a table, which the dashboard does by default and which the Security Advisor insists on, Postgres only returns rows that some policy allows for the current role. With RLS on and no policies, the answer is "no rows". It's not an error, it's a filter, which is why you get [] and not a 403.
That's also why it "works" in the SQL Editor: the editor runs as the postgres role, which bypasses RLS.
Check it
-- Is RLS on, and which policies exist?
SELECT relname, relrowsecurity FROM pg_class WHERE relname = 'todos';
SELECT policyname, cmd, roles, qual, with_check FROM pg_policies WHERE tablename = 'todos';
Or open Authentication → Policies in the dashboard.
Write the policy you actually need
Let signed-in users read and manage their own rows:
