Skip to content

Supabase Row Level Security — Protect Your Database Row by Row

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Supabase Row Level Security. We cover key concepts, practical examples, and best practices to help you master this topic.

Supabase Row Level Security (RLS) uses PostgreSQL policies to restrict which rows users can read, write, update, or delete based on their authentication status and custom conditions.

What You'll Learn

By the end of this lesson you will enable RLS on tables, write security policies for all CRUD operations, use auth.uid() for user-based access, and test policies with Supabase tools.

Why It Matters

RLS is Supabase's primary security mechanism. Without RLS, anyone with your anon key can read or write all your data. With RLS, each user sees only their own data, even with the same API key.

Real-World Use

DodaZIP users can only see their own ZIP files and processing history. The RLS policy checks auth.uid() = user_id on every query, preventing data leaks between tenants.

flowchart LR
    U1[User A] -->|Query files| API[Supabase API]
    U2[User B] -->|Query files| API
    API -->|RLS Policy| DB[(PostgreSQL)]
    DB -->|uid matches| U1
    DB -->|uid matches| U2
    style API fill:#3ecf8e,color:#fff

Enabling RLS

Every table must have RLS enabled before policies take effect.

-- Enable RLS on a table
ALTER TABLE files ENABLE ROW LEVEL SECURITY;

-- For new projects, RLS is enabled by default on all tables
-- Verify RLS status
SELECT relname, relrowsecurity 
FROM pg_class 
WHERE relname = 'files';
# rls_enable.py
# Understanding RLS enablement

def rls_checklist():
    items = [
        "ALTER TABLE files ENABLE ROW LEVEL SECURITY",
        "Create SELECT policy for read access",
        "Create INSERT policy for write access",
        "Create UPDATE policy for modify access",
        "Create DELETE policy for remove access",
        "Test policies with anon key in browser",
        "Test policies with authenticated user",
    ]
    
    print("RLS Setup Checklist:")
    for item in items:
        print(f"  [ ] {item}")

rls_checklist()

SELECT Policies

Control which rows users can read.

-- Allow users to read their own files
CREATE POLICY "Users can view own files"
ON files FOR SELECT
USING (auth.uid() = user_id);

-- Allow users to read any public files
CREATE POLICY "Users can view public files"
ON files FOR SELECT
USING (is_public = true);

-- Allow service role to read all files
CREATE POLICY "Service role can view all files"
ON files FOR SELECT
USING (auth.role() = 'service_role');
# select_policy.py
# Understanding SELECT policies

def explain_select_policy():
    policy_sql = """
    CREATE POLICY "Users can view own files"
    ON files FOR SELECT
    USING (auth.uid() = user_id);
    """
    
    print("SELECT Policy Breakdown:")
    print(f"  Policy name: Users can view own files")
    print(f"  Table: files")
    print(f"  Operation: SELECT")
    print(f"  Condition: Only rows where auth.uid() equals user_id column")
    print()
    print("SQL:")
    print(policy_sql)

explain_select_policy()

INSERT Policies

Control which rows users can create.

-- Allow users to insert their own files
CREATE POLICY "Users can create own files"
ON files FOR INSERT
WITH CHECK (auth.uid() = user_id);

-- Allow authenticated users to insert any file
CREATE POLICY "Authenticated users can insert"
ON files FOR INSERT
WITH CHECK (auth.role() = 'authenticated');
# insert_policy.py
# INSERT policy example

def write_insert_policy():
    sql = """
    CREATE POLICY "Users can insert own files"
    ON files FOR INSERT
    WITH CHECK (auth.uid() = user_id);
    """
    
    print("INSERT Policy:")
    print("  Ensures users can only create rows where user_id matches their UID")
    print("  WITH CHECK validates the new row against the condition")
    print()
    print("Key difference from SELECT:")
    print("  SELECT policies use USING (existing rows)")
    print("  INSERT policies use WITH CHECK (new rows)")

write_insert_policy()

UPDATE and DELETE Policies

Control modification and removal of rows.

-- Allow users to update their own files
CREATE POLICY "Users can update own files"
ON files FOR UPDATE
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);

-- Allow users to delete their own files
CREATE POLICY "Users can delete own files"
ON files FOR DELETE
USING (auth.uid() = user_id);
# update_delete_policy.py
# UPDATE and DELETE policies

def write_update_policy():
    print("UPDATE Policy Example:")
    print()
    print("  USING (auth.uid() = user_id) -- which rows can be updated")
    print("  WITH CHECK (auth.uid() = user_id) -- what values can be set")
    print()
    print("DELETE Policy Example:")
    print()
    print("  USING (auth.uid() = user_id) -- which rows can be deleted")
    print()
    print("Note: DELETE only needs USING, not WITH CHECK")

write_update_policy()

Common Mistakes

  1. Creating policies but forgetting to enable RLS: Policies have no effect until ALTER TABLE ... ENABLE ROW LEVEL SECURITY is run.

  2. Using the wrong clause: SELECT and DELETE use USING only. INSERT uses WITH CHECK only. UPDATE uses both.

  3. Not testing with the anon key: Test policies by making requests without authentication headers to verify public access is correctly blocked.

  4. Overly permissive policies: Starting with USING (true) grants full access. Write restrictive policies first, then expand as needed.

  5. Forgetting auth.role() checks for service_role: If your backend uses the service_role key, it bypasses RLS. Write policies that explicitly handle this role.

Practice Questions

  1. What does RLS stand for and what does it do? Row Level Security. It restricts which rows a user can access based on the policy conditions.

  2. What SQL command enables RLS on a table? ALTER TABLE table_name ENABLE ROW LEVEL SECURITY.

  3. What is the difference between USING and WITH CHECK? USING filters existing rows (SELECT, UPDATE, DELETE). WITH CHECK validates new or modified rows (INSERT, UPDATE).

  4. How do you reference the current user in an RLS policy? Use auth.uid() which returns the UUID of the authenticated user.

  5. Challenge: Write RLS policies for a multi-tenant application where users belong to organizations. Each user should see only their organization's data.

FAQ

Does RLS add performance overhead?

Minimal. PostgreSQL enforces policies at the query planning stage with negligible overhead.

Can I use RLS with the service_role key?

The service_role key bypasses all RLS policies by default. You can write policies that check for service_role.

How do I test RLS policies?

Use the SQL Editor to query as different roles, or test via the API with different auth tokens.

Can RLS policies call functions?

Yes. RLS policies can call PostgreSQL functions, including security definer functions for complex logic.

What happens when RLS is disabled?

The table becomes publicly accessible to anyone with the anon key, exactly as if no security exists.

Mini Project

Create RLS policies for a collaborative document application where:

  • Users can read documents shared with them
  • Users can create their own documents
  • Document owners can update and delete their documents
  • Document collaborators can edit shared documents
def document_rls_policies():
    policies = [
        "CREATE POLICY read_shared ON docs FOR SELECT USING (auth.uid() = owner_id OR auth.uid() IN (SELECT user_id FROM doc_collaborators WHERE doc_id = id))",
        "CREATE POLICY create_own ON docs FOR INSERT WITH CHECK (auth.uid() = owner_id)",
        "CREATE POLICY update_own ON docs FOR UPDATE USING (auth.uid() = owner_id OR auth.uid() IN (SELECT user_id FROM doc_collaborators WHERE doc_id = id AND role = 'editor'))",
        "CREATE POLICY delete_own ON docs FOR DELETE USING (auth.uid() = owner_id)",
    ]
    
    print("Document App RLS Policies:")
    for p in policies:
        print(f"  {p}")

document_rls_policies()

What's Next

Next: Authentication for implementing user auth.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro