What is RLS in Supabase? A beginner's guide
Short answer: RLS (Row Level Security) is a Postgres feature you turn on per table in Supabase. Once it's on, every request through Supabase's API is checked against rules you write, called policies, which decide which rows each user can read or change. If a table in an exposed schema like public has RLS off, any role that's been granted access to it can read and write all of it.
Last checked: Oct 17, 2026 · Source: Supabase docs: Row Level Security
Why RLS matters in a Supabase app
Many Supabase apps talk to the database straight from the browser using the public anon (publishable) key. That key is public by design, so anyone can find it in your app's code (here's why that's expected). What stops a stranger from reading your users' data isn't the key. It's RLS plus the permissions (grants) on each table.
If you built your app with Lovable or another AI builder, the same applies. RLS is a setting on your own Supabase project, and you, the builder, decide whether it's on and what the policies allow.
How RLS works, in plain English
Think of a policy as an automatic filter added to every query. If a policy says "users can see rows where user_id is their own ID," then when a signed-in user asks for all rows, Postgres quietly returns only theirs.
Two checks happen before anyone touches a table:
| Layer | What it decides | Set with |
|---|---|---|
| Grants | Whether a role can select, insert, update or delete on the table at all | grant / revoke |
| RLS policies | Which rows that action applies to | alter table ... enable row level security + create policy |
Supabase recommends setting both for every table you expose. Adding policies does not take away grants, so a table with policies but broad grants can still allow more than you meant.
The roles you'll write policies for
anon: a request from someone who isn't signed in.authenticated: a request from a signed-in user.service_role(or the newer secret key): for your server only. It bypasses RLS completely, so it must never be in browser or app code.
How to enable RLS in Supabase (step by step)
Run these in your own project's SQL Editor, or better, in a migration so every environment gets the same setup. This example is for a todos table where each signed-in user manages only their own rows.
1. Turn on RLS.
alter table public.todos enable row level security;
Heads-up: once RLS is on, the public key can't read or write anything in that table until you add policies. If a feature suddenly shows empty results, that's usually why.
2. Set the grants to match your app. Remove what the public roles don't need, then grant back only what they do:
revoke all on table public.todos from anon, authenticated; grant select, insert, update, delete on table public.todos to authenticated;
Whether new tables get automatic grants depends on your project's settings and on Supabase's October 30, 2026 change (our explainer). Setting grants yourself, as above, works either way.
3. Add one policy per action.
create policy "Users can view their own todos" on public.todos for select to authenticated using ( (select auth.uid()) = user_id ); create policy "Users can create their own todos" on public.todos for insert to authenticated with check ( (select auth.uid()) = user_id ); create policy "Users can update their own todos" on public.todos for update to authenticated using ( (select auth.uid()) = user_id ) with check ( (select auth.uid()) = user_id ); create policy "Users can delete their own todos" on public.todos for delete to authenticated using ( (select auth.uid()) = user_id );
usingdecides which existing rows a user can act on.with checkdecides what a new or changed row is allowed to look like. It stops someone from creating a row "owned" by another user.- Updates also need a matching select policy to work as expected.
4. Test it. Supabase recommends writing tests for each table's policies and running them with supabase test db (see Testing your database). Check both sides: that the owner can do what they should, and that another user can't.
Data that everyone should be able to read
For truly public data, like a list of blog posts, you can allow reads for everyone and keep writes off:
alter table public.posts enable row level security; revoke all on table public.posts from anon, authenticated; grant select on table public.posts to anon, authenticated; create policy "Anyone can read posts" on public.posts for select to anon, authenticated using ( true );
Only use using ( true ) for data that's meant to be public. On a table with personal data, it lets everyone read every row.
Beginner mistakes to avoid
- RLS on, but no policies. Your app stops reading that table. That's safe, but it breaks the feature.
- A policy that allows everything (
using ( true )) on private data. - Leaving out
to authenticated. Name the role in every policy so it only runs for the people it's meant for. - Forgetting that
auth.uid()is empty for signed-out visitors. A policy comparing it touser_idsimply matches nothing for them. That's usually what you want, but be deliberate about it. - Basing a policy on
user_metadata. Users can edit their own user metadata, so it isn't safe for permissions. Supabase suggests app metadata for authorization data. - Views that skip RLS. By default, views don't apply the underlying table's policies. On Postgres 15+, create views
with (security_invoker = true). - Putting the service_role/secret key in frontend code. It bypasses every policy. More in Is the Supabase anon key safe?
Permission denied vs. empty results: a missing grant gives a 42501 permission denied error. A policy that matches no rows just returns nothing. If something fails, check the grants before you rewrite the policy.
How to check RLS in your own app
These checks run inside your own Supabase project. Only check projects you own or have permission to test.
1. Run the Security Advisor in your Supabase dashboard. It flags common problems such as tables in public with RLS off, policies that allow unrestricted access (like using ( true )), and views that bypass RLS.
2. See which tables have RLS on:
select tablename, rowsecurity as rls_enabled from pg_tables where schemaname = 'public' order by tablename;
3. Read your policies:
select tablename, policyname, roles, cmd, qual, with_check from pg_policies where schemaname = 'public' order by tablename;
Look for tables with no policies, policies with no role named, and true on private data.
4. Run your policy tests with supabase test db after every schema change.
Want a quick outside check too? Run a free Greenlight scan to check the basics (HTTPS, security headers, exposed keys). It's not a full security audit.
Paid reports: coming soon.
FAQ
What is RLS in Supabase?
Row Level Security is a Postgres feature that Supabase uses to control which rows each user can read or change through the API. You turn it on per table and write policies for it.
Is RLS on by default in Supabase?
Don't assume it is. Check each table: run the Security Advisor or the pg_tables query above, and enable RLS on every table in an exposed schema such as public.
What happens when I enable RLS without policies?
The public (anon/publishable) key can no longer read or write that table. Your data is protected, but features that use that table will stop working until you add policies.
Do I need RLS if my app has a login?
Yes. A login tells Supabase who the user is. RLS decides which rows they can see. Without it, a signed-in user could reach other users' rows if the table is granted to signed-in users.
Does RLS apply to the service_role key?
No. The service_role (secret) key bypasses RLS, which is why it must stay on your server and never ship in your app.
What's the difference between grants and RLS policies?
Grants decide whether a role can use a table at all. Policies decide which rows. You need both, and adding policies doesn't remove existing grants.
Does Greenlight check my RLS policies?
No. Greenlight's free scan checks the basics from the outside (HTTPS, security headers, exposed keys). It doesn't log into your Supabase project or read your policies, so use the Security Advisor and the steps above for that.
Sources
- Supabase docs: Row Level Security
- Supabase docs: Securing your API
- Supabase docs: API keys
- Supabase docs: Advisors (Security Advisor)
- Supabase docs: Testing your database
Related on Greenlight: Is the Supabase anon key safe to expose? · Will Supabase's October 30 change break your app?
Greenlight is independent and not affiliated with or endorsed by Supabase or Lovable. Product names are used only to describe compatibility. This page is educational, not a security audit or a security guarantee.