So you vibecoded an app, pushed it live, and it's getting real users. Congrats — that's genuinely exciting. But here's a question worth pausing on: Can your users read each other's data?
If you're using Supabase and haven't thought carefully about Row Level Security (RLS), the honest answer might be yes—and that’s a serious problem. Exposing user data doesn’t just break trust; it can also violate privacy regulations, leading to fines and reputational damage.
This article breaks down what RLS is, why it matters, how it can get misconfigured, and how to fix it — without a security engineering background.
**
First, a Quick Bit of Context
**
Supabase is a Backend-as-a-Service (BaaS) platform built on top of PostgreSQL, one of the world's most popular open-source relational databases. Think of Supabase as Firebase, but open-source and SQL-native. It gives you a database, authentication, file storage, and cloud functions — all in one place.
The key thing that makes Supabase different from a traditional backend setup is this: your frontend talks directly to the database. There's no custom server in the middle filtering what data goes where. That's what makes it fast to build with—but it also shifts the responsibility for data access control directly onto you, the app builder.
That's where Row Level Security comes in.
What Is Row Level Security (RLS)?
Row Level Security is a PostgreSQL feature that lets you define rules controlling which rows of a database table a given user can read, insert, update, or delete.
Think of it like a bouncer for every row in your table. Instead of letting anyone with database access query everything, RLS checks: "Is this user allowed to see this specific row?" — before returning any data.
In a practical app context, an RLS policy might say:
- "A user can only read their own chat sessions."
- "A user can only update their own profile."
- "Only admins can delete records."
Without RLS, none of those restrictions exists. Every authenticated user — and sometimes even unauthenticated ones — can potentially access all rows in all tables.
Why This Is a Bigger Deal Than You Might Think
In a traditional architecture, the flow looks like this:
`Client → Your Server → Database`
Your server is where you write the logic that says, "only return data belonging to this user." The database never talks to the client directly.
Supabase flips this:
`Client → Database (via Supabase API)`

Caption: Here are the two architectures side by side — the traditional architecture, Client → Server → Database flow on the left, and the Supabase direct-access model on the right, with the Client → Database.
There's no custom server acting as a gatekeeper. If you haven't defined access rules at the database level, the database has no way of knowing that it shouldn't let User A peek at User B's records.
Without RLS enabled, here's what's potentially possible:
- Any user can query and read all rows in your tables
- A malicious user can download your entire user table
- Users can modify or delete data that doesn't belong to them
- Sensitive data — payment info, private messages, personal details — is exposed
This isn't theoretical. A growing number of apps built quickly with AI assistance and shipped without security review have had data breaches for exactly this reason.
How Does RLS Get Misconfigured?
There are two common failure modes:
1. RLS Was Never Enabled
Supabase does not enable RLS by default on existing tables. For newly created tables, there's a setting to auto-enable them — but many developers either miss this or don't have it turned on. The result is an open table with no restrictions.
You can easily enable RLS when you create a new project:
To check whether RLS is enabled on a table, check the Authentication → policies.
Then you can decide to enable it.
Or open the Supabase Table Editor and review the table settings. Alternatively, you can run this in the SQL Editor:
SELECT tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public';
If rowsecurity shows false for any of your tables, you have a problem.
2. RLS Is Enabled, But No Policies Are Defined
This is a subtle but critical gotcha: enabling RLS without adding any policies blocks all access by default — including your legitimate users.
Once RLS is enabled, the database starts in a "deny everything" mode. You must explicitly write policies that grant access. If you enable RLS and forget to add policies, your users won't be able to read their own data either.
How to Fix It
Step 1: Enable RLS on Your Tables
For existing tables, run this in the Supabase SQL Editor:
ALTER TABLE public.table_name ENABLE ROW LEVEL SECURITY;
Replace table_name with the name of your table. Repeat for every table that holds user data.
Step 2: Add Policies
A policy is an SQL rule attached to a table. It runs every time the table is accessed. Here are the most common patterns you'll need:
Allow users to read their own rows:
CREATE POLICY "Users can view their own sessions"
ON public.table_name
FOR SELECT
USING (auth.uid() = user_id);
Allow users to insert their own rows:
CREATE POLICY "Users can create their own sessions"
ON public.table_name
FOR INSERT
WITH CHECK (auth.uid() = user_id);
Allow users to update their own rows:
CREATE POLICY "Users can update their own sessions"
ON public.table_name
FOR UPDATE
USING (auth.uid() = user_id);
Allow users to delete their own rows:
CREATE POLICY "Users can delete their own sessions"
ON public.table_name
FOR DELETE
USING (auth.uid() = user_id);
auth.uid() is a Supabase function that returns the ID of the currently authenticated user. By comparing it to the user_id column on each row, ensuring users can only access their own data. You should also replace user_id with the column that references auth.uid() in your table.
You can also create policies directly in the Supabase dashboard under Authentication → Policies, where you'll find templates for the most common patterns. Read them carefully before applying — templates are a starting point, not always a perfect fit for your schema.
Step 3: Move Sensitive Data Out of Public Schema (Optional but Recommended)
If your tables contain data that should never be accessible from the frontend at all — things like subscription tiers, rate limits, internal pricing logic — consider moving them to a private schema entirely:
ALTER TABLE public.subscriptions SET SCHEMA private;
Tables in the private schema are not accessible via Supabase's auto-generated API, providing an extra layer of protection regardless of your RLS configuration.
A Note on Using AI to Help
If you're already using Supabase, you can ask the Supabase AI Assistant directly to help you generate the right RLS policies for your tables. Claude Code and tools like Cursor can also review your schema and surface potential access control issues.
That said, treat AI-generated policies as a first draft. LLMs are good at generating standard patterns, but they don't know your specific business logic. Think through questions like:
- Should free-tier users access the same rows as paid users?
- Should admins be able to see all user data?
- Are there rows that should be readable by the public (e.g., published posts) vs. private?
These are product decisions that require your judgment, not just a SQL template.
Quick Reference Checklist
Before your next deployment, run through this:
- [ ] RLS is enabled on all public tables that hold user data
- [ ] SELECT, INSERT, UPDATE, and DELETE policies are defined for each table
- [ ] Policies use
auth.uid()to scope access to the current user - [ ] Sensitive internal tables (pricing, rate limits, etc.) are in the private schema
- [ ]You've tested each policy by logging in as a test user and verifying you can't access another user's data
Wrapping Up
Row Level Security isn't optional if you're shipping real apps with real users. The good news is that it's not complicated to implement — a few SQL statements and some deliberate thinking about who should access what is all it takes.
Supabase makes building fast remarkably easy. Taking one afternoon to lock down your data access rules is how you make sure what you build stays safe for the people using it.
Comment if you’ve had RLS nightmares or successes. Have questions about your specific schema or RLS setup? Drop them in the comments.
Top comments (0)