Guides / RLS Disabled in Public

last checked nov 2026

Supabase "RLS Disabled in Public": what it means and how to fix it

Security Advisor shows RLS Disabled in Public (lint 0013_rls_disabled_in_public). Supabase's Advisors docs summarize the same finding as Table publicly accessible. A table in an exposed schema such as public has Row Level Security turned off, so anyone with your project URL can create, read, update, and delete every row in that table. Enable RLS, then add policies before your app can use the table again through the API.

The short answer

Supabase's Security Advisor flags this as an ERROR. The official summary is "Table publicly accessible." The ramification, in Supabase's words: anyone with your project URL can read, edit, and delete all data in this table because Row-Level Security is not enabled. Tables in the public schema are reachable over the Data API, so leaving RLS off is a critically unsafe configuration.

This is not the same as a using (true) policy. That case is lint 0024 (RLS is on, but a policy lets every row through for the roles it names). Here RLS is absent entirely. Related Advisor findings you might see next to it:

Your publishable (or legacy anon) key is public by design. It is only safe when every exposed table has RLS and policies that match who should see which rows.

How to check

1. Security Advisor (dashboard or CLI)

In the Supabase dashboard open Advisors → Security Advisor and look for lint 0013_rls_disabled_in_public, titled "RLS Disabled in Public" or summarized as "Table publicly accessible." From the CLI:

supabase db advisors

You can also pull advisors through the Management API or MCP get_advisors with type security. Treat every ERROR finding against your intended access model before you dismiss it.

2. List public tables with RLS off

Run this in the SQL Editor. It prints table names and whether RLS is enabled, never keys or row data:

select tablename, rowsecurity
from pg_tables
where schemaname = 'public'
order by tablename;

rowsecurity = false is the condition Advisor is warning about. Optionally confirm what the anon role can still do at the grant layer (grants are separate from policies):

select tablename,
       has_table_privilege('anon', format('public.%I', tablename), 'select') as anon_select,
       has_table_privilege('anon', format('public.%I', tablename), 'insert') as anon_insert,
       has_table_privilege('anon', format('public.%I', tablename), 'update') as anon_update,
       has_table_privilege('anon', format('public.%I', tablename), 'delete') as anon_delete
from pg_tables
where schemaname = 'public'
  and rowsecurity = false
order by tablename;

How to fix it

1. Enable RLS on every flagged table

alter table public.your_table enable row level security;

Replace your_table with each table Advisor listed. After this, the anon role cannot read or write that table through Supabase APIs until you create row level security policies. If you enable RLS and stop there, your app will look empty or fail writes. That is expected, not a reason to turn RLS back off.

2. Set grants, then add owner policies

Postgres checks grants before policies. On many projects a new public table still has broad grants to anon and authenticated. Supabase's RLS guide recommends enabling RLS, revoking, then granting only what each role needs, in the same migration:

alter table public.your_table enable row level security;

revoke all on table public.your_table from anon, authenticated;

-- Example: signed-in users manage their own rows; signed-out visitors get nothing.
grant select, insert, update, delete on table public.your_table to authenticated;

Then add a policy per operation. Prefer naming the role with to, and wrap auth.uid() in (select …) so it runs once per statement:

create policy "Users can view their own rows"
on public.your_table for select
to authenticated
using ( (select auth.uid()) = user_id );

create policy "Users can create their own rows"
on public.your_table for insert
to authenticated
with check ( (select auth.uid()) = user_id );

create policy "Users can update their own rows"
on public.your_table for update
to authenticated
using ( (select auth.uid()) = user_id )
with check ( (select auth.uid()) = user_id );

create policy "Users can delete their own rows"
on public.your_table for delete
to authenticated
using ( (select auth.uid()) = user_id );

Adapt the column name if your owner column is not user_id. For a longer checklist covering grants, the four-policy pattern, and supabase test db, see Supabase Row Level Security for vibe-coded apps.

3. Optional: keep the table off the Data API entirely

If the table should never be reached through the client APIs, Supabase also allows removing its schema (for example public) from Exposed schemas in API settings. That makes every entity in the removed schema inaccessible over APIs. Most apps still need public exposed, so prefer RLS plus policies for tables the app uses.

Prove it / common mistakes

Turning protection off is not a fix for other errors either. Disabling RLS to clear an insert or recursion failure can make the table readable and writable by any role that still holds a grant, which on many projects includes anon. Keep RLS on and fix the policy.

next step

Want a second pair of eyes?

Grab the free Lockdown Pack (security rules for Cursor and Claude Code plus a pre-launch checklist), or have us check your repo with a $99 Scan + Report.

Sources

Every Postgres and Supabase detail above was checked against these official pages. Last checked Nov 2026. If behavior changed, the docs below are the source of truth.