Most people hear "row-level security" and assume they need a database PhD.
You don't. You need about an hour and three policies you'll reuse forever.
RLS isn't database wizardry. It's a tiny set of rules that decide who is allowed to see and change each row. That's the whole concept.
Related reading from vi3ecoding:
- The Supabase Auth Setup I Use on Every Project
- The Lovable + Supabase Stack
- Is Supabase Hard to Learn?
I get asked about this every week, usually right after someone realises they shipped an app where any logged-in user could read every other user's data. Let's make sure that's not you.
What RLS actually is
Row-level security is Postgres saying: "Before I hand back this row, let me check if the person asking is allowed to see it."
Without RLS, every authenticated user can see every row in a table. That's fine for a public blog. It's catastrophic for anything with private data.
Supabase turns RLS on per table. Until you write policies, the table is locked. That default is a feature, not an annoyance.
The mental model: bouncer at the door
Stop thinking about RLS as "database stuff." Start thinking about it as a bouncer.
- The bouncer reads the auth token of the person at the door.
- The bouncer checks one rule per request: "Are you on the list for this row?"
- If yes, you get in. If no, the row doesn't exist as far as you're concerned.
That's it. Every RLS policy you'll ever write is some version of that rule.
The 3 policies that cover most apps
After enough Vi3ecoding client projects, I noticed I was writing the same three patterns over and over. These three cover something like 80% of real-world apps.
Pattern 1 — "Users can only see their own rows"
For tables like profiles, notes, bookings.
create policy "users see their own"
on public.notes for select
to authenticated
using (auth.uid() = user_id);
One line. Done. Combine with insert/update/delete versions of the same and you're shipped.
Pattern 2 — "Members of an org can see org rows"
For multi-tenant apps. Each row has an org_id. A user belongs to one or more orgs through a join table.
create policy "org members can read"
on public.invoices for select
to authenticated
using (
exists (
select 1 from public.org_members
where org_members.org_id = invoices.org_id
and org_members.user_id = auth.uid()
)
);
This is the pattern that runs foodla.eu — restaurants can read their own data, never each other's.
Pattern 3 — "Public read, owner write"
For things like public profiles, marketplace listings, blog comments.
create policy "anyone can read"
on public.listings for select
to anon, authenticated
using (true);
create policy "owners can write"
on public.listings for update
to authenticated
using (auth.uid() = owner_id);
Two policies, two responsibilities. Cleaner than trying to overload one.
The mistake everyone makes once
Storing a role like is_admin directly on the profiles table, then writing an RLS policy that reads it.
Don't.
Roles live in their own table (user_roles), checked through a security-definer function. Otherwise a clever attacker flips their own admin flag and your whole RLS model collapses. This is one of those mistakes that's invisible until it isn't.
How I test RLS before shipping
A 5-minute ritual.
- Log in as User A. Create a row. Confirm you can see it.
- Log in as User B. Confirm you cannot see User A's row.
- Try to update User A's row as User B. Confirm it fails.
- Try the same as an anonymous visitor. Confirm it fails.
If any of those four checks doesn't behave, the policy is wrong. Don't ship until they all do.
When RLS feels too restrictive
It almost always means a missing table, not a missing policy.
If you're writing weird OR clauses to make one policy cover three cases, split the data. A separate shared_with join table is almost always cleaner than a clever single policy.
So — do you need a database PhD for Supabase RLS?
No.
You need three patterns, an hour of practice, and the habit of testing as two users before you ship.
RLS feels intimidating because it sits next to a lot of intimidating SQL. The thing itself is small. Treat it that way and it stops being scary.
vi3ecoding Team