Skip to content

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) and with 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 check applies on insert/update, using applies on select/update/delete.
  • Avoid using true on 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/*.sql and include them in migrations.
  • Use supabase db push to 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_role key in frontend.
  • Always enable RLS on sensitive tables.

References