Skip to main content
Row Level Security (RLS) is PostgreSQL’s most powerful security feature, providing granular control over which rows users can access in your database. When properly configured, RLS ensures users can only see and modify data they’re authorized to access.

What is Row Level Security?

RLS is a PostgreSQL feature that adds an implicit WHERE clause to every query based on policies you define. This means security is enforced at the database level, protecting your data even if accessed through third-party tools.
RLS works with Supabase Auth to provide end-to-end security from the browser to the database.

Why RLS is Critical

Tables in the public schema without RLS enabled are accessible to anyone with your anon key.Supabase allows browser access to your database for convenience, but this requires RLS to be secure.
RLS provides:
  • Defense in depth: Protection even if application code has vulnerabilities
  • Automatic enforcement: No way to bypass security from client code
  • Granular control: Different rules for SELECT, INSERT, UPDATE, DELETE
  • Third-party protection: Security maintained when using external tools

Enabling RLS

Enable on Individual Tables

Once RLS is enabled, no data is accessible via the API until you create policies.

Auto-Enable RLS on New Tables

Create an event trigger to automatically enable RLS:
This trigger only affects tables created after installation. Enable RLS manually on existing tables.

Creating Policies

Policies define the rules for accessing rows. Each policy has:
  • Table: Which table it applies to
  • Operation: SELECT, INSERT, UPDATE, or DELETE
  • Role: Which database role (anon, authenticated)
  • Condition: SQL expression that must be true

SELECT Policies

Control which rows users can view:

INSERT Policies

Control which rows users can create:
WITH CHECK ensures the new row data meets policy requirements.

UPDATE Policies

Control which rows users can modify:
UPDATE operations require a matching SELECT policy to work properly.

DELETE Policies

Control which rows users can delete:

Common Policy Patterns

User-Owned Data

Team-Based Access

Role-Based Access

Multi-Factor Authentication Required

Helper Functions

auth.uid()

Returns the ID of the authenticated user:
auth.uid() returns NULL for unauthenticated users, which causes policies to silently fail. Always check for NULL:

auth.jwt()

Accesses JWT claims:

Performance Optimization

RLS policies can impact query performance. Follow these practices:

1. Add Indexes

Index columns used in policies:
Impact: 99.94% faster queries

2. Wrap Functions with SELECT

Improve function performance:
Impact: 95% faster queries
Wrapping with SELECT causes PostgreSQL to cache the function result per statement instead of calling it for each row.

3. Add Filters to Queries

Even with policies, always filter in your queries:
Impact: 95% faster queries

4. Use Security Definer Functions

Bypass RLS for complex joins:
Never put security definer functions in schemas exposed via PostgREST (like public). Use a private schema.

5. Minimize Joins

Rewrite policies to avoid table joins:
Impact: 99.78% faster queries

6. Specify Roles

Always use TO clause:
Impact: 99.78% faster for anon users (policy skipped entirely)

Testing RLS Policies

Manual Testing

Test as different users:

Automated Testing with pgTAP

Bypassing RLS

Service Role Key

The service_role key bypasses all RLS:
Never use service_role key in client-side code! This gives unrestricted database access.Safe usage:
  • Server-side code only
  • Admin tools
  • Background jobs
  • Database migrations

Custom Roles with Bypass

Create roles that bypass RLS:

Troubleshooting

Cause: RLS enabled but no policies createdSolution:
Cause: Multiple policies are combined with OR logicSolution:
  • Check all policies on the table
  • Use RESTRICTIVE policies for AND logic
  • Test with specific roles: SET ROLE authenticated
Cause: Missing indexes or inefficient policiesSolution:
  • Add indexes on columns used in policies
  • Wrap functions with SELECT
  • Minimize table joins
  • Use security definer functions
  • Always add filters to queries
Cause: User not authenticated or token expiredSolution:

Best Practices

1

Enable RLS on all tables

Never leave tables in public schema without RLS
2

Start restrictive, then open up

Begin with policies that deny everything, then add access:
3

Test policies thoroughly

Test as different user types before deploying
4

Monitor performance

Use Performance Advisor to find slow policies
5

Document complex policies

Add comments explaining business logic

Next Steps

Encryption

Learn about data encryption in Supabase

Network Security

Configure IP restrictions and SSL

Testing Guide

Set up automated RLS testing

Performance Tuning

Optimize RLS policy performance

Additional Resources