Row Level Security (RLS) is a database feature, built into Supabase's underlying Postgres, that controls which rows each request is allowed to read or write, enforced by the database itself. Instead of trusting your application code to filter data, you write policies on each table so a user can only ever touch the rows they are permitted to, no matter how the request arrives, including a request that goes straight to Supabase with the public anon key.
Why it exists
Application code that filters data can be bypassed if someone talks to the database directly, for example with a public API key. RLS moves the rule down into the database, so the restriction holds even when the request does not come through your app. It is the difference between a lock on the door and a sign asking people not to enter.
How to enable RLS in Supabase
In the Supabase dashboard, open Authentication, then Policies (or the Table editor), pick a table, and turn Row Level Security on. That alone denies all access, so the next step is required: add a policy. A minimal owner-scoped policy looks like this, run in the SQL editor or added through the Policies UI:
-- Enable RLS on the table
alter table public.posts enable row level security;
-- Allow a user to read only their own rows
create policy "select own rows"
on public.posts for select
using (auth.uid() = user_id);
-- Allow a user to insert only rows they own
create policy "insert own rows"
on public.posts for insert
with check (auth.uid() = user_id);How it works in practice
You enable RLS on a table, then add policies such as allow a user to select rows where the row's user_id equals their authenticated id. With no policy, an enabled table denies everything; with a permissive allow-all policy, it is the same as having RLS off, which is a common mistake.
The mistake that undoes it
Enabling RLS is only half the job. An enabled table with no policy denies everyone, so a common quick fix is to add a policy that allows everything just to make the app work again. That reopens the exact hole RLS was meant to close. A real policy has to scope access to the current user, usually by comparing a column like user_id to the authenticated id.
What each RLS state actually allows
| Table state | What the public anon key can do |
|---|---|
| RLS off | Read and write every row, no restrictions |
| RLS on, no policy | Nothing: all access is denied by default |
| RLS on, allow-all policy | Read and write everything (same as off, a common mistake) |
| RLS on, scoped policy | Only the rows the policy permits (correct) |
What this means for AI-generated code
This matters for AI-built apps because tools like Lovable and Bolt ship a public Supabase key in the client, and that key is only safe if RLS is configured. AI builders almost never set it up, so the default is a public key in front of an unprotected database. That single gap drives a large share of the flaws we find.
Common questions
How do I know if RLS is enabled on a table?
In the Supabase dashboard, open Authentication, then Policies, or the Table editor: each table shows whether RLS is on and lists its policies. A table marked with RLS disabled is fully readable and writable by the public anon key.
Is the Supabase anon key safe if RLS is on?
Yes. The anon key is public by design, and with RLS enabled and scoped policies on every table, it can only do what your policies allow. The danger is a public key in front of tables that have RLS off or an allow-all policy.
Does Row Level Security slow down my database?
The overhead is usually small. Policies run as part of the query, so keeping them simple and indexing the columns they filter on (like user_id) keeps them fast. The safety is worth far more than the marginal cost.
Do I still need RLS if I only use the service_role key on a server?
If every request goes through your own server using the service_role key and your server enforces access, RLS is less critical, but it is still a valuable backstop. The moment any public anon key touches those tables, RLS is the only thing protecting them.
What is a permissive or allow-all policy?
A policy whose condition is always true (for example USING (true)), so it permits every row for everyone. It makes an RLS-enabled table behave as if RLS were off. Replace it with a policy that scopes rows to the authenticated user.
How do I disable RLS in Supabase?
In the Table editor or with alter table public.your_table disable row level security; in the SQL editor. There is rarely a good reason to do this on a production table with a public anon key, since it removes the only thing stopping that key from reading and writing every row. It is sometimes used briefly in local development, never on a table a browser client can reach.
Is there a tool to check if my Supabase tables are actually protected?
Yes. Prbl's free Supabase RLS checker takes your app's URL, finds the public anon key, and tells you whether RLS is actually enforcing anything or whether an anonymous request can read your tables, instead of leaving you to check each table by hand.