Home / RLS / Oct 30 grants change
Supabase RLS · Platform change
Supabase's Oct 30 grants change: what breaks, and the fix that leaks
From Oct 30, a new table in public isn't reachable by anon / authenticated until you GRANT it. Existing tables keep working. Put grant + RLS + policies in the same migration. Don't fix a 42501 with grant ... on all tables: that also grants every table that has RLS off.
01 What changes on October 30
Until now, every table created in public got full privileges (select / insert / update / delete and more) granted to anon, authenticated and service_role automatically, through default privileges. On October 30, 2026 Supabase turns that off for all existing projects (new projects have had it off since a gradual rollout that started May 30). From then on:
New tables
Created without a GRANT, they're invisible to the Data API (supabase-js, PostgREST, GraphQL), however you created them: SQL editor, migration, CLI, MCP or an AI tool.
Existing tables
Not affected. They keep the grants they have today, so your running app keeps working.
RLS and policies
Unchanged. Grants decide whether a role can touch the table at all; RLS decides which rows.
Direct Postgres connections (psql, an ORM, a server with a connection string) aren't affected. Source: Supabase's official announcement.
02 What "broken" looks like
The first new table after the cutover returns this from the API instead of rows:
response (example from Supabase's announcement)
{
"code": "42501",
"message": "permission denied for table invoices",
"hint": "Grant the required privileges to the current role with: GRANT SELECT ON public.invoices TO anon;"
}
Postgres rejects the query before RLS even runs. That's the new default working as intended, not a bug in your policies.
03 The fix that leaks
The fastest way to make that error go away is the bulk grant from the rollback section of the announcement:
don't paste this as a fix
grant select, insert, update, delete on all tables in schema public to anon, authenticated, service_role;
It works, and it also grants every table in public, including the ones you never meant to expose. Any of those with RLS off becomes readable and writable by anyone holding your anon key, which ships in your frontend. The new default was your chance to stop exposing tables by accident; the bulk grant gives that back. Ask an AI coding tool to "fix the permission error" and this line is a likely answer.
Grant per table instead, and only what each role needs.
04 The migration pattern that works
Treat grant, RLS and policies as one unit, in the same migration, so a table never exists half-configured:
migration.sql
create table public.invoices ( id bigint generated always as identity primary key, user_id uuid not null references auth.users(id), amount numeric not null ); alter table public.invoices enable row level security; create policy "owner reads" on public.invoices for select to authenticated using (user_id = (select auth.uid())); -- only the role and the operations the app actually uses grant select on public.invoices to authenticated;
No grant to anon here, because signed-out visitors don't read invoices. If a table uses serial / bigserial instead of an identity column and the API inserts into it, the sequence needs its own grant: grant usage, select on sequence public.invoices_id_seq to authenticated;
05 Check what the API roles can reach today
Run this in the SQL editor. It lists every table and view in public, whether anon or authenticated can read it, and whether RLS is on:
check.sql
select c.relname, has_any_column_privilege('anon', c.oid, 'select') as anon_can_read, has_any_column_privilege('authenticated', c.oid, 'select') as auth_can_read, c.relrowsecurity as rls_on from pg_class c join pg_namespace n on n.oid = c.relnamespace where n.nspname = 'public' and c.relkind in ('r', 'p', 'v', 'm') order by 1;
A table with anon_can_read = true and rls_on = false can be read by anyone with your anon key. Two details make this stricter than the usual query built on information_schema.table_privileges:
Column grants
grant select (id, email) on profiles to anon doesn't show up as a table privilege, but the API still serves those columns. has_any_column_privilege catches table and column grants alike.
Views
Views are reachable through the API too, and by default a view reads its tables as the view's owner, which skips RLS. Create views with security_invoker = on so they respect the caller's RLS.
06 Keep it from coming back
The new default only protects you while every migration stays honest. Two free checks help there:
In CI
npx airlock-migrate reads your migration files and fails the build when one creates a table without enabling RLS, or turns RLS off. Those are the tables a bulk grant would expose. It also warns on a view without security_invoker = on. It doesn't flag grants themselves, and it only sees tables that go through your migration files.
On the live database
SUPABASE_DB_URL=... npx airlock-rls checks the running database for tables with RLS off and policies that let everyone through. Set it up in CI.
Catch this before it ships
airlock-rls is a CI gate that fails your build when a table ships exposed or a policy is permissive: the same class of bug, caught in CI against a database with your migrations applied, before it reaches prod.
Or start from nextjs-supabase-starter : auth + a table with RLS + an isolation test, so you start from a pattern that's already sealed.
Grab the Supabase RLS cheat sheet
The golden rules, the footguns that leak in prod, correct policy snippets, and the isolation test — on one page.
FAQ
Do my existing tables stop working on October 30?
No. Supabase only changes the default for tables created after the cutover. Existing tables keep their current grants. The first thing that breaks is the next new table you create without a GRANT.
Does this change make my app more secure by itself?
Only for new tables. Every existing table keeps its old grants, so one that has RLS off today stays readable through the API after October 30. Run the check above and enable RLS on anything that shows anon_can_read true with rls_on false.
Should I grant to anon or to authenticated?
Grant to the role that actually needs the table. Data only signed-in users see gets a grant to authenticated and no grant to anon. Public data, like a product catalog, can be granted to anon with an RLS policy that allows reading it.
Is service_role affected?
Yes. It also loses the automatic grants on new tables, so a backend using the service_role key gets 42501 on a new table until you grant it. Once granted, it still bypasses RLS.