Supabase · row-level security
Your app logs in fine and shows nothing
The sign-in works. The dashboard loads. Every table is empty, the console is clean, and the same query returns rows in the Supabase editor. This is the single most common thing we see in Lovable and Bolt apps, and it is almost never a bug in your code.
Usually under two hours of work. If your data model needs reshaping instead, we will say so on the call.
- First step
- A short call, or a written description if you would rather not talk yet
- Who you speak to
- The engineer who would do the work. No account manager, no sales call.
- Turnaround
- Most single problems are resolved within 48 hours of getting access
- Cost
- Quoted after we have seen it — scope varies, and guessing helps neither of us
How to get this fixed
Already a client? Raise it as a ticket in Golux Club and it enters the queue with an SLA attached.
Why it happens, and why there is no error
PostgREST does not refuse a query that row-level security filters out — it answers 200 with an empty array. From the browser that is indistinguishable from 'there are no rows'. So a policy that is missing, or that compares the wrong column, looks exactly like an empty database. Three variants cover almost every case: RLS was switched on without any SELECT policy, so nothing matches; the policy compares `auth.uid()` to a column that holds something else (an email, a profile id, a tenant id) so it never matches; or the table is queried with the anon key from a public page while the policy requires an authenticated role.
How to tell it is this
- The same query returns rows in the Supabase SQL editor but not in the app
- The network tab shows 200 with `[]`, not 401 or 403
- It started right after someone 'turned on RLS because the security scan complained'
- Some tables work and others do not — the ones that work were never locked down
- An INSERT succeeds and then the row is invisible to the user who created it
What we do
In order, and we stop at the first one that explains it.
- 01
Reproduce it against your project
Same query, anon key, logged-out and logged-in. Two minutes, and it tells us which of the three variants you have.
- 02
Read every policy on the affected tables
What the predicate compares, what the roles are, whether INSERT and UPDATE have matching rules. Usually the mismatch is visible here.
- 03
Write the policy you needed
Scoped to the right column, with the roles stated explicitly, and USING and WITH CHECK both covered so inserts do not disappear.
- 04
Prove it with a test you keep
A query with an anonymous key that must fail, and one with a user's key that must succeed. You get both, so the next person cannot quietly undo it.
What you get
- The data showing, for the right users and nobody else
- The policies written out, with a line on what each one allows
- Two checks you can run yourself to confirm it is still right
- A note on any other table with the same problem waiting to happen
When this is not what you need
- Nothing shows for anyone, logged in or not, and the table is genuinely empty — that is a data problem, not a policy one
- Your schema has no column that identifies the owner of a row; the policy cannot be written until the data model is — that is bigger work, and we will say so
- You want every table reviewed rather than the one that is broken — the hardening sprint does that properly
How it works
- 01
Tell us the symptom
One paragraph is enough — the error, the screen, what you expected. Run the free audit first if you have not; it often names the problem for you.
- 02
A call, or a written reply
Thirty minutes to look at it together, or a written answer if you prefer. Either way you find out what it is before anything is agreed.
- 03
A written offer
After the call: the scope, the approach and what we need from you. Discussed on the call, confirmed in writing.
- 04
Fixed, explained, handed back
The fix in your project, a short note on what was wrong and why, and how to check it yourself — typically inside 48 hours of getting access.
What we promise before any money changes hands
We tell you what is actually wrong, whether it is one problem or a symptom of a bigger one, and what it would take — before you commit to anything. If the honest answer is that you can fix it yourself in an afternoon, that is the answer you get, and the instructions are usually on this page already.
Questions
- Is this a security problem or just a bug?
- Both, usually in sequence. The app worked because RLS was off and every row was readable by anyone with the anon key; turning it on broke the reading and fixed the exposure. The right end state is policies that let the right people read and nobody else — which is what we write.
- Will you need write access to my database?
- To apply it, yes, or you apply the SQL we hand you — both are fine. To diagnose it, read access to the schema and the policies is enough.
- How do I know it will not happen again?
- Because you keep the two checks. A policy change that breaks reading, or opens it too far, fails one of them. That is the difference between a fix and a patch.
- Is this one table or all of them?
- One problem — in practice that is the broken behaviour, across whatever tables are causing it. If the whole schema needs policies written from scratch, that is a bigger job, and we will tell you so on the call rather than stretching this one.
Other things we fix at the same price
- A secret key is in my bundle“There is a secret key in my JavaScript bundle.”
- Login loops or logs me straight out“Signing in bounces me back to the login page, or a refresh logs me out.”
- I have no real backups“If the database disappeared tomorrow, I do not know what I would do.”
Next step
Tell us the symptom. We will tell you if it is one problem.
We answer within a few hours with what it is, what it would take, and what we need from you. If it is bigger than one problem, we say that instead — before anything is agreed.

