Supabase RLS Policy Examples for Beginners: Read, Insert, Update, and Delete
Dev
Last updated on

Supabase RLS Policy Examples for Beginners: Read, Insert, Update, and Delete


RLS is the point where many Supabase projects stop feeling simple and start feeling broken. Data still exists, but the app suddenly returns nothing because the request no longer matches a policy.

This guide gives the beginner mental model first, then walks through simple select, insert, update, and delete examples so you can understand what changes after RLS is enabled.


Quick Answer

If Supabase starts returning no rows after you enable RLS, the database is often doing exactly what you asked it to do.

The safe beginner path is simple: enable RLS, add a read policy first, then add insert, update, and delete policies with explicit ownership checks. Test each step with a real authenticated session before adding more complexity.

What to Check First

Use this order when RLS first becomes confusing:

  1. confirm RLS is enabled on the table
  2. confirm the request is really authenticated
  3. check how auth.uid() is expected to match the row owner
  4. add select policy first before debugging updates
  5. verify browser code is using the public client, not the service role key

If those five are not clear, most RLS bugs will feel random.

What RLS is doing

Supabase uses PostgreSQL Row Level Security to control which rows a request can access.

The simplest mental model is this: a policy behaves like an extra filter that gets applied to table access.

That is why a table can look normal in the dashboard but return nothing in the app.

How to enable RLS

The official Supabase docs show that you can enable RLS with:

alter table profiles enable row level security;

Supabase also notes an important consequence: after RLS is enabled, the API will not expose rows to the browser through the public anon key until you create policies.

That is the behavior that surprises most beginners.

Why auth.uid() matters

Supabase provides helper functions such as auth.uid() so policies can compare the current authenticated user to a row owner.

A common pattern is:

using ((select auth.uid()) = user_id)

Supabase’s docs also point out that unauthenticated requests make auth.uid() return null, so policies should often be explicit about authentication instead of assuming it.

Which policy shape fits which operation

OperationMain policy toolWhy
selectusingControls which existing rows can be seen
insertwith checkControls which new rows are allowed
updateusing + with checkControls both target rows and changed rows
deleteusingControls which existing rows can be removed

This is the part many beginners miss: insert and update do not behave exactly like select.

Example table

Imagine a simple table like this:

create table profiles (
  id uuid primary key,
  user_id uuid references auth.users,
  display_name text
);

Then enable RLS:

alter table profiles enable row level security;

Example 1: allow users to read only their own rows

create policy "Users can read their own profile"
on profiles
for select
to authenticated
using ((select auth.uid()) is not null and (select auth.uid()) = user_id);

This is a good first pattern because it is easy to reason about.

Example 2: allow users to insert their own rows

create policy "Users can insert their own profile"
on profiles
for insert
to authenticated
with check ((select auth.uid()) is not null and (select auth.uid()) = user_id);

For inserts, with check is the important part because it controls what new rows are allowed.

Example 3: allow users to update their own rows

create policy "Users can update their own profile"
on profiles
for update
to authenticated
using ((select auth.uid()) is not null and (select auth.uid()) = user_id)
with check ((select auth.uid()) is not null and (select auth.uid()) = user_id);

One subtle point from the Supabase docs: updates generally need the right read access expectations too. If your select logic is missing, update behavior may not work the way you expect.

Example 4: allow users to delete their own rows

create policy "Users can delete their own profile"
on profiles
for delete
to authenticated
using ((select auth.uid()) is not null and (select auth.uid()) = user_id);

This is the same ownership model, applied to deletion.

A practical beginner sequence

If you are new to RLS, this is a safe order:

  1. create the table
  2. enable RLS
  3. add the select policy
  4. add insert and update policies
  5. test with a real authenticated session
  6. confirm browser code is using the public client, not the service role key

That sequence is easier to debug than writing every policy at once, so use it before you try more complex access rules.

What not to do

1. Do not leave RLS enabled with no policies and assume the app is broken

Often the app is fine. The table is just correctly denying access.

2. Do not use the service role key in the browser

Supabase’s docs are very clear that service keys can bypass RLS and should not be exposed to end users.

3. Do not forget the to authenticated role

Supabase recommends scoping policies to roles explicitly when possible.

4. Do not rely on auth.uid() = user_id without thinking about unauthenticated requests

If no user is signed in, the expression does not behave like an ownership pass.

Bottom Line

For beginners, RLS becomes manageable when you stop treating it like magic and start treating it like row-level filtering with explicit ownership checks.

In practice, enable RLS, add a simple read policy first, test with a real authenticated user, then layer insert, update, and delete rules one step at a time.

FAQ

Q. Why does Supabase return no rows after I enable RLS?

Because enabling RLS without policies blocks browser access through the public key by default.

Q. What is the difference between using and with check?

using controls which existing rows can be touched or seen. with check controls which new or changed rows are allowed.

Q. Can I use the service role key to make the problem disappear?

Not in browser code. That is unsafe and defeats the point of RLS.

Sources:

Start Here

Continue with the core guides that pull steady search traffic.