Am I Hackable?
Back to Learn

What is Row Level Security (RLS) in Supabase?

Benji··3 min read

The short version

Row Level Security (RLS) is a PostgreSQL feature that controls which rows a user can access in a table. Instead of checking permissions in your application code, you define policies directly in the database. The database enforces them on every query, no matter how the data is accessed.

In Supabase, RLS is critical because your database is exposed to the internet via the auto-generated API. Without RLS, your anon key (which is meant to be public) gives anyone full read/write access to your tables.

Why it matters in Supabase

Supabase generates a REST API (PostgREST) and a real-time API directly from your database schema. Your frontend talks to these APIs using the Supabase client library. The anon key is embedded in your frontend code, visible to anyone.

Without RLS enabled, here's what someone can do with just your project URL and anon key:

// Anyone can do this from their browser console
const { data } = await supabase.from('users').select('*')
// Returns EVERY row in your users table

The Supabase RLS documentation makes this clear: "If you have RLS disabled, anyone with the project's URL and anon key can read and modify data in your database."

How RLS works

RLS uses policies, which are SQL expressions that evaluate to true or false for each row. You create separate policies for SELECT, INSERT, UPDATE, and DELETE.

Here's a simple example: users can only see their own data.

-- Enable RLS on the table
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;

-- Create a policy: users can only read their own profile
CREATE POLICY "Users can view own profile"
  ON profiles
  FOR SELECT
  USING (auth.uid() = user_id);

auth.uid() is a Supabase function that returns the currently authenticated user's ID. The policy says: "For each row, check if the user_id column matches the logged-in user. If yes, they can see it. If no, the row is invisible."

As documented in the PostgreSQL RLS documentation, when RLS is enabled on a table, all normal access must pass through the policies. No policy means no access (except for table owners).

Common patterns

Public read, authenticated write:

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

-- Only authenticated users can insert
CREATE POLICY "Auth insert" ON posts
  FOR INSERT WITH CHECK (auth.role() = 'authenticated');

Users own their data:

CREATE POLICY "Own data" ON todos
  FOR ALL USING (auth.uid() = user_id);

Admin access:

CREATE POLICY "Admin full access" ON orders
  FOR ALL USING (
    EXISTS (
      SELECT 1 FROM user_roles
      WHERE user_id = auth.uid() AND role = 'admin'
    )
  );

The mistakes that get people

Forgetting to enable RLS. Creating policies does nothing if RLS is not enabled on the table. Always run ALTER TABLE ... ENABLE ROW LEVEL SECURITY first.

Not creating any policies after enabling RLS. RLS with no policies means zero access for everyone (except the table owner). Your app will silently return empty results.

Trusting the frontend for authorization. RLS is the real access control layer in Supabase. Client-side checks are for UX only. A user can always open the browser console and make direct API calls.

Forgetting about the service_role key. The service_role key bypasses RLS entirely. Never expose it in your frontend. It belongs in server-side code only.

The bottom line

If you're using Supabase, RLS is not optional. It's the difference between a database that's protected and one that's an open spreadsheet on the internet.

Check your site

Want to know if your site has this issue? Scan it now and find out in 60 seconds.

Frequently Asked Questions

What is Row Level Security?
RLS is a PostgreSQL feature that lets you define policies controlling which rows each user can see, insert, update, or delete. It moves access control into the database itself.
Is RLS enabled by default in Supabase?
No. When you create a new table in Supabase, RLS is disabled by default. You must enable it and create policies. Without RLS, your table is accessible to anyone with your API URL and anon key.
Can I skip RLS and just protect my API?
In theory, yes. In practice, Supabase exposes your database directly via its auto-generated REST API. If RLS is off, anyone with your anon key (which is public) can read and write everything.

Your AI writes the code. We find what it missed.

Paste your URL. Security audit in 60 seconds.

Scan my app