Silence is the symptom
In Supabase the browser talks to Postgres through an interface limited by row-level security. When protection is on and no policy matches, the answer is not an error but an empty result. PostgreSQL documents the default as deny, and Supabase documents that with RLS enabled and no policies nothing is accessible with the browser-side key until policies exist. So "the rows are in the dashboard but my app shows none" is the expected result of enabling protection without a matching policy, and the cause is usually a signed-out session, the wrong role, a missing operation or a column that does not match.
- Check the session reaches the call: auth.uid() is null when no one is signed in.
- Check the policy names the signed-in role and covers the operation.
- Check the owner column and its type.
The two shortcuts to refuse
Switching protection off and putting the secret key in browser code both make data appear, and both give every visitor access to every row the key reaches. Supabase documents the secret key as bypassing row-level security and says it must stay server-side. If either has been done on a table with customer data, treat it as an incident and rotate the key; only you can do that. The repair for an empty list is a narrow policy proved with two test users, not a wider key.
- A key found in browser code should be treated as exposed.
- Do not base policies on data users can edit about themselves.
Pick the smallest job that fits
One table and one access rule, such as each user sees only their own rows, is the Supabase access repair (from £395, untested, quoted after we read the table and the rule): delivered as a migration you run yourself and accepted by a two-user test and a signed-out request. Payments that do not update access are a different job on the existing Stripe, Next.js and Supabase offer. Any other reproducible bug is a bug repair with a regression test (from £295). A group of flows that must all work is a project (from £3,500, quoted). Prices are untested and paid only after sign-off. Send the table, the owner column and the rule in plain words, not keys or real rows.
- This is not a security audit.
- We are a new service with no client delivery record to show.
Sources and limits
- Supabase: Row Level Security Checked 2026-10-11.
- With RLS enabled and no policy, no data is accessible with the publishable key; auth.uid() is null when no one is signed in; the secret key bypasses RLS.
- Supabase: API keys Checked 2026-10-11.
- The publishable key is safe in the browser; the secret key bypasses RLS and must stay server-side.
- PostgreSQL 18: Row Security Policies Checked 2026-10-11.
- Row security is default-deny when enabled and no policy exists.