Checklist
How to set up Supabase row-level security for an AI-built app
Short answer. Turn on Row Level Security for every table the browser can reach, revoke the default grants, and grant back only what that role should do. Then add a policy that checks the signed-in user. Supabase says a policy does not remove a grant, and the secret key bypasses the policies entirely.
Updated 21 Oct 2026
This is a practical checklist, not a guarantee. It is not a penetration test, a compliance audit, or proof that an app is safe to launch.
The row level security checklist is the pass you run after the policies exist. This page is how to create them on a project an AI builder just made. Official steps are in Supabase's Row Level Security guide.
Lovable Cloud and many Bolt databases speak this model. A Cursor project does too, if you connected Supabase. The client should use the publishable key. That key is public. The policies are the control.
See how the table was created
Supabase's API security docs say tables created in the Dashboard have RLS enabled by default. Tables created in the SQL editor, or by a migration an agent wrote, need you to enable RLS yourself.
Open Database, then Policies, and check the table. You can also run alter table ... enable row level security in the SQL editor. Supabase says that once RLS is on, the publishable key sees no rows until a policy allows them.
On existing projects, a new table in public can already grant every privilege to anon, authenticated, and service_role. The privileges include select, insert, update, and delete. Adding a policy does not revoke those grants. A table with a careful policy and a leftover anon insert grant still accepts that insert.
Do grants and policies together
Supabase's guide says to do this for every exposed table, in one migration if you want it repeatable:
1. Enable RLS. 2. Revoke existing grants from anon and authenticated. 3. Grant back only the operations that role needs. 4. Write one policy per operation.
This is the shape of their documented example. Rename reports and owner_id to your table. Signed-out visitors get nothing. Signed-in users can operate on their own rows.
alter table public.reports enable row level security;
revoke all on table public.reports from anon, authenticated;
grant select, insert, update, delete on table public.reports to authenticated;
create policy "Users read their own reports"
on public.reports
for select
to authenticated
using (owner_id = (select auth.uid()));Add insert, update, and delete policies the same way. An insert policy uses with check, not only using. A select policy does not cover insert. using (true) allows every row the role can already reach.
service_role bypasses RLS. Supabase says to keep the secret key and the legacy service-role key on the server. If the agent put that key in the client, follow how to keep API keys out of AI-generated code and rotate it.
Match the policy to the app
| Who calls the table | Grant | Policy |
|---|---|---|
| Signed-out visitors, public marketing rows only | select to anon | A condition that is true only for those public rows |
| Signed-in users, their own rows | The operations the screen needs, to authenticated | owner_id = (select auth.uid()) or a team membership check |
| A server job that must see every row | No browser grant | The secret key on the server, which bypasses RLS, with the check written in that function |
Supabase maps a request with the publishable key and no user to the anon role, and a signed-in user to authenticated. The user id in the policy is auth.uid().
If the screen only reads, grant select. Granting insert "so the demo works" is how a generated client gets a write path you did not design.
Prove the two roles
Supabase's guide says to add a SQL test under supabase/tests/ that asserts allow and deny for select, insert, update, and delete, for both anon and authenticated, then run supabase test db. Until that passes, the policy is a guess.
If you are not using the CLI, do the same test in the app:
- Create two users.
- As user A, insert a row.
- As user B, request that row with the same publishable key the app ships.
- Repeat while logged out.
An empty result is the pass. A 200 that contains user A's email is the fail. The checklist has the storage and view cases this setup page does not repeat. Views and security definer functions can bypass the table policy. Test those with the publishable key too.
Builders add tables in later prompts. Re-open Policies after each one. A new table with RLS off is back to a public table for anyone who has the project URL and the publishable key.
FAQ
The app broke when I turned RLS on. Should I turn it off?
No. Supabase's docs treat a missing grant or a missing policy as the thing to fix. A missing grant returns a permission error. A policy that matches no rows returns an empty result. Check the grant first, then the policy. Disabling RLS makes the table reachable by any role that still has a grant.
Does Lovable or Bolt do this for me?
Lovable says it sets up policies when it stores user data, and that you should review them. The policies screen in Lovable Cloud is read-only. You change them by asking in the project chat, then you test with a second user. Bolt says it enables RLS on tables it creates, and that a table without RLS can be read and changed by anyone. Tables you or the agent add later still need the switch. Details are in the Lovable guide and the Bolt guide.
Can I use the service role in the browser if it is in an env file?
No. Supabase says the secret key and the service-role key are never safe to expose, because they bypass RLS. An env name that the framework inlines, such as VITE_ or NEXT_PUBLIC_, ships the value to the browser. Keep that key in a server environment only.
Is a policy of "must be signed in" enough?
Only if every signed-in user is allowed to see every row. Supabase's authenticated role is every logged-in user, not "this user." Compare auth.uid() to an owner column, or to a membership table. The database and auth category is the rest of the products if this project is not on Supabase.
Sources
Checked 21 Oct 2026.
Newsletter
A short weekly note on new tools and security checklists. No spam, unsubscribe anytime.