Supabase Row Level Security Explained With Real Examples

When I first started using Supabase, one of the most confusing (but also powerful) features I came across was Row-Level Security (RLS). At first, it felt a bit technical, but once I understood it properly, I realised how important it is for protecting data. In this blog, I’ll give an overview of what RLS is, why it matters, and show real examples of how I use it in projects.
What is Row-Level Security (RLS)?
In simple words, RLS controls which rows of a table a user can access. Instead of handling all permission checks in your frontend or backend code, you define rules directly inside the database.
For example, if you have a todos
table where each row belongs to a specific user, RLS can make sure users only see their own todos — not someone else’s.
Supabase uses PostgreSQL’s RLS feature under the hood, so you get the same reliability and security but with an easier developer experience.
Why RLS is important (my perspective)
I see RLS as a safety net for my apps. Here’s why:
- Centralised rules – I don’t need to repeat permission checks in multiple parts of my code.
- Stronger security – Even if someone finds a bug in my API, the database itself won’t return data they shouldn’t see.
- Easier scaling – If I add more features or clients later, the same RLS policies still apply.
Without RLS, you risk exposing sensitive data by mistake. With RLS, the database itself enforces who sees what.
How RLS works in Supabase
There are three main things to understand:
- Enable RLS on a table – By default, it’s off. You need to enable it to apply policies.
- Policies – These are rules you define, like “users can only select rows where
user_id = auth.uid()
.” - Roles – Supabase has built-in roles like
anon
(not logged in) andauthenticated
(logged in). You can target policies for each role.
Once you enable RLS and add policies, Supabase automatically enforces them on all queries.
Real Examples of RLS
Here are three examples that I find most useful in real projects.
Example 1: Per-user data (Todos app)
Here, each logged-in user can only see and add their own todos. Even if they try to query all todos, the database will only return rows matching their user_id
.
Example 2: Admins vs normal users
Let’s say you have a projects
table:
- Admins can see all projects.
- Regular users can only see projects they own or are members of.
This way, admins have full access, while regular users are restricted to their own projects.
Example 3: Multi-tenant SaaS (Company separation)
In SaaS apps, you often need to keep each company’s data separate. You can add a tenant_id
column and restrict rows by tenant.
Now, even if two companies are on the same database, they’ll never see each other’s data.
Common mistakes I’ve seen (and made myself)
When I was learning RLS, I ran into these issues:
- Forgetting to enable RLS – Policies won’t work until you explicitly enable it.
- Too broad policies – Writing
USING (true)
means every logged-in user can access all rows (bad idea). - Performance problems – If policies use complex joins without indexes, queries get slow. Always index columns like
user_id
ortenant_id
. - Assuming RLS replaces all backend logic – RLS is strong, but sometimes you still need app-level checks for complex workflows.
Best practices that help me
- Enable RLS from day one – Don’t leave it for later.
- Keep policies simple – Start with basic checks, then refine.
- Use custom JWT claims – Store roles or tenant IDs in tokens so policies don’t need heavy subqueries.
- Add indexes – Any column used in policies should be indexed.
- Test with different users – Supabase dashboard lets you test queries as specific users.
What’s new in 2025
A few updates I’ve noticed in the Supabase community:
- More developers are using JWT claims to simplify policies and improve performance.
- Supabase added detailed docs on RLS performance best practices (like indexing and keeping expressions simple).
- Developers are mixing RLS with backend APIs for complex apps, instead of relying only on database rules.
Final thoughts
For me, Supabase RLS is one of those features that feels tricky at first but pays off massively once you get the hang of it. It keeps your data safe, simplifies your code, and makes scaling easier.
If you’re starting a new project, I’d suggest:
- Enable RLS early.
- Keep policies clear and test them often.
- Use indexes and JWT claims for performance.
Done right, RLS acts like a solid security guard for your database — always on duty, making sure no one sees what they shouldn’t.