Row Level Security is the single most important security feature in Supabase, and it is the one that vibe coders most often skip. If you are building an app with Supabase and you have not set up RLS, your database is effectively public. Any user — or any person who inspects your frontend code — can read, modify, or delete every row in every table. Not a hypothetical risk. A real, exploitable vulnerability that has already burned real projects.

This guide explains RLS in plain language, shows you why it matters with concrete examples, walks you through enabling it step by step, covers the most common policy patterns, and teaches you how to use AI tools to write policies so you do not have to become a SQL expert.

What Is Row Level Security?

Row Level Security is a PostgreSQL feature that lets you control which rows in a table each user can access. Without RLS, every query returns every row. With RLS, the database checks each row against a set of rules (called "policies") and only returns the rows the current user is allowed to see.

Think of it like this: your database is an apartment building. Without RLS, every tenant has a master key that opens every apartment. With RLS, each tenant's key only opens their own apartment. The building (database) enforces the access rules, not the tenants (your frontend code).

This distinction matters because Supabase exposes your database directly to the browser via its JavaScript client. Your frontend makes queries like supabase.from('posts').select('*'). Without RLS, that query returns every post from every user. With RLS, it returns only the posts the current user is allowed to see.

The Real Risk: What Happens Without RLS

In May 2025, security researchers published findings showing that a significant number of apps built with Lovable and other app builders had no RLS enabled on their Supabase databases. The impact was stark: anyone could open their browser's developer tools, find the Supabase URL and anon key in the JavaScript bundle (these are always visible — they are meant to be), and use them to query the entire database directly.

What an attacker could do with no RLS:

This is not an edge case. It is the default behavior. When you create a new table in Supabase, RLS is disabled by default. Supabase shows a warning about this in the dashboard, but many vibe coders click past it, especially when AI tools generate the database schema automatically.

Step-by-Step: Enabling RLS on Your Tables

Here is how to enable RLS and create your first policies using the Supabase dashboard. No command line required.

Step 1: Open the Table Editor

Go to your Supabase project dashboard. Click Table Editor in the left sidebar. Select the table you want to secure.

Step 2: Enable RLS

At the top of the table view, you will see a banner that says "RLS is disabled for this table." Click the Enable RLS button. Supabase will warn you that enabling RLS without policies will block all access. That is correct and expected — it is the secure default.

Important: Once RLS is enabled with no policies, your frontend queries will return zero rows. This is by design. A table with RLS enabled and no policies denies all access. You need to add policies to grant specific access.

Step 3: Create Your First Policy

Click New Policy and choose from the templates or write a custom policy. The most common first policy is: "Users can read their own data."

Here is what that looks like in SQL:

CREATE POLICY "Users can view their own data"
ON public.profiles
FOR SELECT
USING (auth.uid() = user_id);

What this does: when anyone runs a SELECT query on the profiles table, PostgreSQL checks each row and only returns rows where the user_id column matches the ID of the currently authenticated user. auth.uid() is a Supabase function that returns the current user's ID from their JWT token.

Step 4: Test Your Policy

Supabase has a built-in SQL editor. Use it to test:

-- This should return only the current user's profile
SELECT * FROM profiles;

You can also test from your app by logging in as different users and checking what data each user sees. If User A cannot see User B's data, your policy works.

Common RLS Policy Patterns

Most apps need the same five or six policy patterns. Here they are with SQL examples you can copy directly.

Pattern 1: Users Own Their Data

The most common pattern. Users can only read, insert, update, and delete their own rows.

-- Read own data
CREATE POLICY "Users read own data" ON public.todos
FOR SELECT USING (auth.uid() = user_id);

-- Insert own data
CREATE POLICY "Users insert own data" ON public.todos
FOR INSERT WITH CHECK (auth.uid() = user_id);

-- Update own data
CREATE POLICY "Users update own data" ON public.todos
FOR UPDATE USING (auth.uid() = user_id);

-- Delete own data
CREATE POLICY "Users delete own data" ON public.todos
FOR DELETE USING (auth.uid() = user_id);

Pattern 2: Public Read, Authenticated Write

Blog posts, product listings, public profiles — anyone can read, but only authenticated users can create or modify.

-- Anyone can read
CREATE POLICY "Public read" ON public.posts
FOR SELECT USING (true);

-- Only authenticated users can insert
CREATE POLICY "Auth users insert" ON public.posts
FOR INSERT WITH CHECK (auth.uid() IS NOT NULL);

Pattern 3: Admin Access

Admins can see and modify everything. Regular users are restricted. This assumes you have a role column on a profiles table.

CREATE POLICY "Admins full access" ON public.orders
FOR ALL USING (
  EXISTS (
    SELECT 1 FROM profiles
    WHERE profiles.id = auth.uid()
    AND profiles.role = 'admin'
  )
);

Pattern 4: Team/Organization Access

Users can access data belonging to their team or organization.

CREATE POLICY "Team members access" ON public.projects
FOR SELECT USING (
  team_id IN (
    SELECT team_id FROM team_members
    WHERE user_id = auth.uid()
  )
);

Pattern 5: Service Role Bypass

Sometimes your backend (edge functions, webhooks) needs full access. The Supabase service role key bypasses RLS automatically. Never use the service role key in frontend code — only in server-side functions.

RLS and Vibe Coding: The AI Problem

Here is the uncomfortable truth: most AI coding tools generate Supabase schemas without RLS policies. They create tables, set up relationships, and generate frontend queries — but they skip the security layer. This is not malice. The AI is optimizing for "make it work" and RLS adds complexity. But the result is deployed apps with no data protection.

The fix is straightforward: after your AI generates a database schema, always follow up with a prompt specifically about security. Here is a template you can use with any AI coding tool:

Review the Supabase schema for this project.
For each table:
1. Enable RLS
2. Create policies so users can only access their own data
3. Create admin policies if there is a role system
4. Ensure no table is publicly writable without authentication
Show me the SQL for all policies.

This prompt works in Cursor, Claude Code, Lovable, and any AI that understands SQL. The key is to make it a separate, explicit step — do not assume the AI handled security during initial setup.

Testing Your RLS Policies

Setting up RLS is half the job. Testing it is the other half. Here is a practical testing checklist:

  1. Log in as User A. Create some data. Verify User A can see it.
  2. Log in as User B. Verify User B cannot see User A's data.
  3. Log out entirely. Verify that unauthenticated requests return nothing (or only public data, if that is what your policies allow).
  4. Try direct API access. Open a new browser tab, go to https://your-project.supabase.co/rest/v1/your_table?select=* with the anon key as a header. If you see data that should be private, your policies have a gap.
  5. Test INSERT and UPDATE. Try inserting a row with a different user's ID. Your policy should reject it.

Supabase also provides an RLS Debugger in the dashboard (Table Editor → Policies → Debug). This lets you simulate queries as different users and see which policies allow or block access. Use it.

Common Mistakes with RLS

Mistake 1: Enabling RLS but adding no policies. This locks everyone out, including your app. You will see empty pages and think the app is broken. The fix: add at least a SELECT policy for each table.

Mistake 2: Using USING (true) for all operations. This is equivalent to not having RLS at all. Only use USING (true) for public read access. Never for INSERT, UPDATE, or DELETE.

Mistake 3: Checking auth on SELECT but not on INSERT/UPDATE. Users can see only their data but can insert data as any user, or update anyone's records. Always create policies for all four operations: SELECT, INSERT, UPDATE, DELETE.

Mistake 4: Storing the service role key in frontend code. The service role key bypasses all RLS. If it is in your JavaScript bundle, anyone can use it to access your entire database. Only use the service role key in server-side code: edge functions, API routes, or backend services.

Mistake 5: Not applying RLS to junction/join tables. If you have a team_members table that links users to teams, it needs RLS too. Otherwise users can add themselves to any team by inserting a row directly.

RLS Performance Considerations

A reasonable concern: does RLS slow down queries? The answer is yes, marginally, but it is almost never a problem in practice. Each policy adds a WHERE clause to every query, and PostgreSQL is very efficient at evaluating these. For most vibe-coded apps with thousands or even tens of thousands of users, you will never notice a performance difference.

The one exception is complex policies with subqueries (like the team access pattern). If your team_members table is large, consider adding an index on the user_id column. Your AI coding tool can help with this — ask it to "add indexes to support the RLS policies."

The Bottom Line

RLS is not optional. It is the difference between a secure app and a data breach waiting to happen. The good news is that it is not difficult. Enable RLS on every table, add the basic "users own their data" policies, test with multiple accounts, and ask your AI to review the policies for gaps. An hour of RLS setup protects your users' data and saves you from becoming a cautionary tale in a security researcher's blog post.

For more on securing your vibe-coded app, see our Security Basics guide and Prototype to Production guide.

Secure Your Stack

RLS is just one layer. Browse our tool directory to find the right auth, database, and monitoring stack for your app.

Browse All Tools