Back to blogs

How I Set Up Policies in Supabase (Step-by-Step Guide)

June 23, 2025
4 min read
How I Set Up Policies in Supabase (Step-by-Step Guide)

When I began working with Supabase, I quickly understood that securing data access is not optional — it’s foundational. Without Row Level Security (RLS), any user or script could potentially read or modify sensitive data, which is a significant security risk. Here’s an in-depth exploration of how I approached RLS, with practical examples, best practices, and recommendations for structuring access control in Supabase.


Why RLS Matters

RLS is a PostgreSQL feature that Supabase builds upon to enforce fine-grained, per-user access rules directly at the database level. Unlike client-side or middleware-based validation, RLS ensures your data is secure by default — even if a request bypasses your frontend or API logic.

This model provides “defense in depth,” ensuring only the right user can read, insert, update, or delete specific rows, regardless of how the request is made.


Step 1: Enabling Row Level Security

By default, RLS is disabled on new tables in Supabase. This means your data could be exposed unless you explicitly enable protection.

To enable RLS:

  1. Open your Supabase project.
  2. Go to the table you want to secure.
  3. Click the “Security” tab.
  4. Enable Row Level Security.

Once enabled, the table becomes locked down by default — no one can access it until you define policies.

Recommendation:

Always enable RLS for every table, even if you think it holds non-sensitive data. You can open access selectively with policies.


Step 2: Defining Policies for Different User Types

Anonymous Read-Only Users

For public-facing content like blogs or open product listings:

create policy "Allow public read"
on my_table
for select
using (true);

This allows read access to everyone, but no inserts, updates, or deletes. Use this cautiously and only for safe, public data.

const supabase = createClient('https://your-project.supabase.co', 'public-anon-key');


Authenticated Users Accessing Their Own Data

Use auth.uid() to ensure users only access their own records:

create policy "Users access their own data"
on my_table
for all
using (auth.uid() = user_id);

To pass user credentials on the frontend:

const supabase = createClient('https://your-project.supabase.co', 'public-anon-key', {
global: {
headers: {
Authorization: `Bearer ${accessToken}`
}
}
});

Supabase usually handles this token injection automatically after login.


Insert Policy for New Records

Ensure users can only insert rows where the user_id matches their authenticated identity:

create policy "Insert own data"
on my_table
for insert
with check (auth.uid() = user_id);


Update/Delete Policies

Allow users to modify or delete only their own rows:

create policy "Update own data"
on my_table
for update
using (auth.uid() = user_id);
create policy "Delete own data"
on my_table
for delete
using (auth.uid() = user_id);


Backend Access Using Service Role

The service role key bypasses all RLS rules and should only be used in trusted, server-side environments (e.g., inside getServerSideProps or API routes):

const supabase = createClient('https://your-project.supabase.co', 'your-service-role-key', {
auth: {
persistSession: false,
autoRefreshToken: false,
detectSessionInUrl: false
}
});

Never expose this key in the frontend.


Advanced Techniques and Best Practices

Combine Multiple Policies

Supabase evaluates policies using OR logic, so you can define multiple layers of access:

create policy "Public can read"
on my_table
for select
using (true);
create policy "Users manage their own"
on my_table
for all
using (auth.uid() = user_id);


Improve Performance with Indexes

Queries filtered by RLS expressions can be slow on large datasets. Index your policy columns:

create index on my_table(user_id);


Test RLS with Multiple Scenarios

You should manually test:

  1. Anonymous access
  2. Logged-in user access
  3. Malicious attempts to query others’ data

Use SQL Editor or Supabase CLI to simulate queries as different roles.


Recommended Project Structure

Keep SQL organized for portability and clarity:

project-root/
lib/
supabase.ts # Supabase client
supabase/
policies/
users.sql # Policy definitions
migrations/
init.sql
enable-rls.sql

This helps you track access logic and apply it consistently across environments.


Summary

Row Level Security is essential for any application handling user data. It enforces a zero-trust approach at the database layer and gives you precise control over who sees or modifies what.

Key takeaways:

  1. Always enable RLS on every table.
  2. Use separate policies for anonymous, authenticated, and admin/service roles.
  3. Never expose service keys to the client.
  4. Optimize with indexes and modular SQL.
  5. Validate every scenario through thorough testing.

When properly implemented, RLS makes your Supabase setup secure, scalable, and production-ready.

Let me know if you want this tailored to a specific project or turned into SQL files ready for import.

Supabase Row Level SecuritySupabase access control policiesSecure Supabase database setupSupabase authentication rulesUser role-based access in Supabase