Supabase Row Level Security: setup, policies, and testing
Enable RLS, tighten Postgres grants, add ownership policies, and test anonymous plus cross-user access. The examples below cover the mistakes that expose real Supabase data.
Supabase secures its Data API with two layers: grants control which operations a role may perform, and RLS controls which rows the operation may reach. You need both. Updated September 2026.
For an implementation checklist and copyable policy patterns, see the same team's practical Supabase RLS guide.
Dashboard tables and SQL-created tables behave differently
Tables created through the Supabase Dashboard have RLS enabled by default. Tables created through SQL, migrations, or external tooling may not. For every table in an exposed schema, enable RLS in the migration, remove unnecessary grants, and test the anon and authenticated roles. A public publishable or legacy anon key is expected; unsafe permissions behind it are the vulnerability.
Not sure if your RLS is configured correctly?
Our scanner tests your live project from the outside, the same way an attacker would. Paste your live app URL and we'll extract the Supabase config from your frontend.
RLS Policy Testing
Tests every table for read/write access
Storage Buckets
Checks upload/delete permissions
RPC Functions
Finds unprotected server functions
CRUD Permissions
INSERT, UPDATE, DELETE checks
Edge Functions
Tests unauthenticated access
Auth Config
CAPTCHA and signup settings
Results in 2-3 minutes; your first scan is free. Point it at your live app URL. vas extracts your Supabase config from your frontend automatically.
Quick Start: Enable RLS in 2 Minutes
1Enable RLS on Your Table
-- Enable RLS on the table
ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;2Create a Policy for Authenticated Users
-- Users can only see their own data
CREATE POLICY "Users can view own data"
ON your_table
FOR SELECT
TO authenticated
USING ((select auth.uid()) = user_id);
-- Users can only insert their own data
CREATE POLICY "Users can insert own data"
ON your_table
FOR INSERT
TO authenticated
WITH CHECK ((select auth.uid()) = user_id);
-- Users can only update their own data
CREATE POLICY "Users can update own data"
ON your_table
FOR UPDATE
TO authenticated
USING ((select auth.uid()) = user_id);
-- Users can only delete their own data
CREATE POLICY "Users can delete own data"
ON your_table
FOR DELETE
TO authenticated
USING ((select auth.uid()) = user_id);Important: Use (select auth.uid()) instead of auth.uid() for better performance. The select wrapper prevents re-evaluation on every row.
3Verify It Works
-- In a local or test environment, simulate a signed-in user
set session role authenticated;
set request.jwt.claims to '{"role":"authenticated","sub":"USER_UUID"}';
-- Assert this user can read their row, but not another user's row
select * from public.your_table;
-- Put repeatable allow and deny assertions in supabase/tests/
-- Then run: supabase test dbManual testing catches simple cases, but misses edge cases like write access, storage buckets, and RPC functions. For a thorough check, run a scan against your live app.
Common RLS Patterns
User-Owned Data
Most common pattern - each user can only access their own rows:
CREATE POLICY "Users own their data"
ON posts
FOR ALL
TO authenticated
USING ((select auth.uid()) = author_id);Public Read, Private Write
Anyone can read, but only owners can modify:
-- Anyone can read
CREATE POLICY "Public read access"
ON posts
FOR SELECT
TO anon, authenticated
USING (true);
-- Only owners can write
CREATE POLICY "Owners can modify"
ON posts
FOR UPDATE
TO authenticated
USING ((select auth.uid()) = author_id);Team-Based Access
Users can access data belonging to their team:
CREATE POLICY "Team members can access"
ON projects
FOR ALL
TO authenticated
USING (
team_id IN (
SELECT team_id FROM team_members
WHERE user_id = (select auth.uid())
)
);Common RLS Mistakes
Forgetting to enable RLS
Creating policies without enabling RLS does nothing. Always run:
ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;Using auth.uid() without select wrapper
This causes performance issues - the function re-evaluates for every row:
-- Bad: USING (auth.uid() = user_id)-- Good: USING ((select auth.uid()) = user_id)Creating service_role policies
Service role bypasses RLS automatically. Adding a policy for it creates warnings:
-- Bad: Creates multiple_permissive_policies warning-- Good: Just use TO authenticated (service_role bypasses RLS)Only testing SELECT policies
Most developers test if users can read other users' data but forget to test INSERT, UPDATE, and DELETE. An attacker who can't read your data can still modify or delete it if write policies are missing.
Our scanner checks for all of these mistakes automatically.
Check Your App for RLS MistakesRLS Security Checklist
Want to check all of these automatically?
Run your first scan freeFrequently Asked Questions
Why use (select auth.uid()) instead of auth.uid() in Supabase RLS?
Wrapping auth.uid() in a SELECT subquery lets Postgres create an initPlan and cache a value that does not change per row. Supabase's performance guidance recommends this pattern for JWT and other stable functions. Also index columns used by policies and measure the real query plan before and after.
What does “RLS disabled in public” mean in the Supabase dashboard?
It means a table in an exposed schema can be reached through the Data API without row policies. Actual access also depends on Postgres grants. Enable RLS with ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;, revoke operations the role does not need, and add explicit policies for everything that remains allowed.
Is RLS enabled by default in Supabase?
Tables created through the Supabase Dashboard have RLS enabled by default. Tables created through SQL, migrations, or external tools may not. Include the RLS statement in the same migration that creates each exposed table so the protection is reproducible.
How do I enable RLS on tables created with SQL or migrations?
Add ALTER TABLE public.your_table ENABLE ROW LEVEL SECURITY; to the migration, set only the grants each role needs, and create policies for each permitted operation. Revoking default privileges can make new exposure opt-in, but it does not enable RLS by itself.
What does “Enable automatic RLS” do in Supabase?
It creates an event trigger that enables Row Level Security on new tables in the public schema. That is a useful safety net, but it does not create policies or configure least-privilege grants. Keep an explicit ALTER TABLE ... ENABLE ROW LEVEL SECURITY statement in every migration too, so the security state is visible and reproducible.
How do I disable RLS in Supabase (and when should I)?
ALTER TABLE your_table DISABLE ROW LEVEL SECURITY; turns it off for a table. The only legitimate case is read-only public reference data (country lists, public product catalogs), and even then a FOR SELECT TO anon, authenticated USING (true) policy is the safer move. Never disable RLS to fix a permission error; that error is RLS doing its job.
Do grants and RLS policies do the same thing?
No. Grants decide whether a Postgres role can perform an operation on a table, view, or function. RLS policies then decide which rows that operation can affect. PostgreSQL evaluates grants first, so a secure review must cover both layers.
How can I test if my Supabase RLS is actually secure?
Add database tests for allowed and denied SELECT, INSERT, UPDATE, and DELETE operations under both anon and authenticated. Use two authenticated users to test cross-user and cross-tenant isolation, then run supabase test db. Vibe App Scanner can also probe the deployed app from an external attacker's perspective.
Related
Free RLS Checker
Test a single table's policies, free, no signup
CVE-2025-48757 breakdown
The Lovable + Supabase RLS bypass in detail
Is Supabase Safe?
Honest security analysis of Supabase
Lovable Statistics
Funding, users, and the CVE-2025-48757 impact
Supabase Security Incidents
Documented breaches and exposures
What Is Row Level Security?
A beginner-friendly explanation of RLS
Stop Guessing. Scan Your Supabase Project.
Paste your live app URL. We'll extract your Supabase config from your frontend and report RLS policies, storage buckets, RPC functions, and more in 2-3 minutes.
Run your first scan freeYour first scan is free. No card required.
Last updated: September 4, 2026