Supabase CLI Row Level Security (RLS) & Policies
This document covers best practices for Supabase RLS, policies, and usage patterns, using todos as example data.
What is RLS?
Row Level Security (RLS) allows you to enforce rules on which rows a user can access. When RLS is enabled, all
select, insert, update, and delete commands are filtered according to policies.
- Enabled table:
ALTER TABLE your_table ENABLE ROW LEVEL SECURITY; - Policies: define
using(for select/update/delete) andwith check(for insert/update) conditions.
RLS is enforced automatically for anon keys; service_role keys bypass it.
Supabase Keys Overview
| Key Type | Usage | RLS Enforcement |
|---|---|---|
| anon (publishable) | Frontend / Expo app | RLS applied |
| service_role | Backend / Server / CI/CD | RLS bypassed (full access) |
Tip: Never expose service_role key in frontend apps.
CLI Commands for RLS & Policies
supabase db push --local→ applies new migrations locally.supabase db reset→ drops local tables and reapplies all migrations.supabase db pull→ pulls schema from cloud to local migration file.supabase db diff -f <name>→ generates migration SQL by comparing schema changes.
Example Todos Table with Policies
Create a new migration:
supabase migration new create_todos_policies
create_todos_policies.sql:
-- Create todos table
create table todos
(
id bigint generated by default as identity primary key,
user_id uuid references auth.users (id) not null,
title text not null,
is_complete boolean default false,
inserted_at timestamp with time zone default now(),
is_public boolean default false
);
-- Enable RLS
alter table todos
enable row level security;
-- Users can view their own todos
create policy "Users can view their own todos"
on todos
for select
using (auth.uid() = user_id);
-- Users can insert todos for themselves only
create policy "Users can insert their own todos"
on todos
for insert
with check (auth.uid() = user_id);
-- Users can update their own todos
create policy "Users can update their own todos"
on todos
for update
using (auth.uid() = user_id);
-- Users can delete their own todos
create policy "Users can delete their own todos"
on todos
for delete
using (auth.uid() = user_id);
-- Optional: public read-only todos
create policy "Anyone can view public todos"
on todos
for select
using (is_public = true);
Notes:
auth.uid()refers to the currently authenticated user.with checkapplies on insert/update,usingapplies on select/update/delete.- Avoid
using trueon sensitive tables in production. - Policies can be expanded to roles, groups, or other conditions.
Migrations & Policies
- Best Practice: Keep RLS policies in the same migration file as the table creation.
- Each migration is feature-specific, e.g.,
20251001125133_create_todos_policies.sql. - For complex projects, you can separate policies into
schemas/policies/*.sqland include them in migrations. - Use
supabase db pushto apply migrations locally or to a linked cloud project.
Best Practices
- Keep migrations + policies in one place for each feature.
- Version control all migrations.
- Avoid exposing
service_rolekey in frontend. - Always enable RLS on sensitive tables.