Skip to content
Throttle Bird

Logbook · Launch notes

Supabase row level security for beginners

What row level security (RLS) actually does, how to turn it on, how to write your first policy, and how to check it worked. Written for people who didn't set up the database themselves.

2026-10-26 · Throttle Bird

If Lovable, Bolt, or another builder set up Supabase for you, you probably never chose your database rules. That's fine. Most people don't. But row level security, or RLS, is the setting that decides whether a stranger can read your users' data, so it's worth understanding once.

This guide skips the theory. It covers what RLS does, how to turn it on, what a policy looks like, and how to check that it worked.

Why your app needs RLS at all

Most vibe-coded apps talk to Supabase straight from the visitor's browser. To do that, your app ships a public key (Supabase calls it the anon or publishable key). Anyone can open your site and find that key. That's normal and expected. The key is meant to be public.

So the key isn't what keeps your data private. RLS is. Supabase's own docs put it bluntly: a table in an exposed schema without RLS is readable and writable by any role with a grant on it. In plain words, if RLS is off, anyone with your public key can read and change that table.

I wrote more about the key itself in The anon key is not the secret.

The three states a table can be in

Every table has its own RLS switch. Each one is in one of three states:

  • RLS off. Anyone with the public key can read, add, change, or delete rows. This is the one to fix.
  • RLS on, no policies. Nobody gets anything through the public key. Your data is safe, but your app may suddenly show empty pages.
  • RLS on, with policies. People can see and change exactly what the policies allow, such as "users can see their own orders."

You want the third state for every table your app uses.

Step 1: Find the open tables

  1. Log in at supabase.com and open your project.
  2. Open the Table Editor. A table with RLS off shows a red Unrestricted label.
  3. Open Advisors → Security Advisor and read anything marked as an error. It flags tables in the public schema with RLS turned off.

Write down every table that's flagged.

Step 2: Turn RLS on

You can do this in the dashboard by opening the table and enabling RLS, or by running one line of SQL in the SQL Editor for each table:

alter table public.orders enable row level security;

Replace orders with your table name. As soon as you run it, the public key can't read anything from that table until you add a policy. If a page in your app goes blank right after this, that's expected. It means the lock works and you now need to say who's allowed in.

Step 3: Write your first policy

A policy is a rule attached to one table. Supabase describes it as adding a WHERE clause to every query. Here's the most common one, which lets signed-in users see only their own rows:

create policy "Users can view their own orders"
on public.orders for select
to authenticated
using ( (select auth.uid()) = user_id );

Read it like a sentence. On the orders table, for reading (select), for signed-in people (authenticated), only show rows where the user_id column matches the person asking. auth.uid() is Supabase's way of saying "the signed-in user."

Supabase recommends one policy per action: one for select (read), one for insert (add), one for update (change), and one for delete. Adding and changing use with check so nobody can create a row that belongs to someone else:

create policy "Users can add their own orders"
on public.orders for insert
to authenticated
with check ( (select auth.uid()) = user_id );

This only works if your table actually has a column like user_id that stores who owns each row. If it doesn't, that's the first thing to fix.

You don't have to write SQL yourself

If you built with an AI tool, you can ask it to do this. A prompt that works well:

Turn on row level security for every table in the public schema. For each table, add separate select, insert, update, and delete policies so signed-in users can only see and change rows where user_id matches their own id. Name the role in every policy. Don't make any table readable by everyone unless I say it's public, and never use the service role or secret key in frontend code.

Then check the result yourself. AI tools sometimes "fix" an access error by making a table readable by everyone, which undoes the whole point.

Four mistakes I see often

  • A policy that says `using (true)`. That means "everyone can read every row." It's fine for a public blog posts table. It's not fine for users, orders, or messages.
  • Using the secret key to make an error go away. The service_role or secret key skips RLS entirely. It belongs on a server, never in the browser.
  • Trusting `user_metadata` in a rule. Supabase warns that users can edit their own user metadata. Don't use it to decide who's an admin.
  • Forgetting views. A view over a protected table can hand out every row unless it's created with security_invoker = true (Postgres 15 and up).

Step 4: Check that it worked

Open your app in a private browser window and sign up as a second person. Try to see the first person's orders, profile, or messages, including by changing an ID in the address bar. If you can see them, a policy is too loose.

Then go back to the Table Editor and confirm no table says Unrestricted.

One more thing worth knowing: from October 30, 2026, Supabase is changing how new tables get access by default. I wrote a separate guide on what that change means for your app.

The short version

  • The public key is supposed to be public. RLS is what protects your data.
  • Turn RLS on for every table, then add one policy per action.
  • Never fix an error by switching to the secret key or by writing using (true) on private data.
  • Test with a second account.

If you'd like a second pair of eyes, the free Throttle Bird scan lists the Supabase tables your public code names. Once you prove you own the app, it checks whether a stranger can read each one, using only the public key and one read per table. How that works is on the Supabase docs page.

Sources: Supabase: Row Level Security, Supabase: Securing your API.

Before you go: the free launch-safety checklist

Five practical steps to check before you launch, with a prompt for each. Free page and PDF, or we'll email it to you.

Check your own app

Paste your URL to run these checks automatically. Free, results in under a minute.

Scan my app free