Supabase security · 5 minute read
Supabase October 30, 2026: new tables throw 42501 until you GRANT
On October 30, 2026 Supabase enforces a breaking change for grants on tables in the public schema. Existing tables keep their grants and stay reachable. New tables created from October 30 onward stop being exposed to the Data API and your code starts failing with {"code":"42501","message":"permission denied for table"}. The fix is five minutes of SQL, not a debugging afternoon.
What is actually changing
Supabase is removing the automatic grants that made every new public table reachable by the anon and authenticated roles through the Data API. Tables that already exist keep whatever grants they have. The first table your migration or AI builder creates after the 30th is the one that breaks.
Grants answer a different question than RLS
RLS policies decide which rows a role may see or touch. Grants decide whether the role may touch the table at all. Most shipped apps have policies but no grants discipline, and this change makes that gap visible for the first time, because the auto-grant you never wrote stops covering you.
The census that shows exactly what breaks
Run this in the SQL Editor (read-only) before you ship anything after October 30. It lists every grant on every public table:
select table_name, grantee, privilege_type
from information_schema.role_table_grants
where table_schema = 'public'
order by table_name, grantee;Any table your client code touches that is missing select, insert, update, delete for the role it actually uses (usually authenticated, sometimes anon) will throw 42501 through the Data API after the change. Also open your project's Security Advisor - it flags tables affected by this change, but only the ones its analyzer has seen.
The fix, per new table
Add all three statements to the same migration as the table, as a unit. A policy without a grant still 42501s; a grant without RLS is an open table:
grant select, insert, update, delete on table public.invoices to authenticated;
grant select, insert, update, delete on table public.invoices to service_role;
alter table public.invoices enable row level security;
create policy "owners read their invoices" on public.invoices
for select to authenticated using ((select auth.uid()) = user_id);
create policy "owners insert their invoices" on public.invoices
for insert to authenticated with check ((select auth.uid()) = user_id);The ALTER DEFAULT PRIVILEGES gotcha
If you do not want to touch every future migration, the shortcut is default privileges:
alter default privileges in schema public
grant select, insert, update, delete on tables to authenticated, anon;
alter default privileges in schema public
grant usage, select, update on sequences to authenticated, anon;The gotcha: default privileges are per creating role. They cover tables created by the role that ran the statement. If your migrations run as postgres but a new table is created by a different role - the dashboard table editor, an AI tool connecting with its own role, another team member's service credential - the defaults you set do not apply to that table and you are back to 42501. Run the statement for every role that creates tables, or keep explicit grants in the migration files as the primary mechanism and defaults as a safety net.
The after-check most people skip
Create a throwaway table in a preview-branch migration, then hit it through the Data API as anon and as authenticated. A 200 from the role your client actually uses is the only verification that counts - the migration running without error only proves the SQL parsed.
The migration kit for this exact change
If you would rather not hand-write the GRANT block per table: I packaged the ready-to-use migration template (explicit grants for every CRUD operation, ALTER DEFAULT PRIVILEGES variants, and the dry-run script with rollback) plus the verification queries into the Supabase October 30 Migration Kit. The dry-run matters: it proves your grants work against a real schema copy without touching production.
Reference
- Supabase changelog #45329 - tables not exposed to Data API/GraphQL automatically.
- The full grants census, policy-versus-grant boundary and the pre-launch checks I run on Supabase apps are documented in my public repo: github.com/cekuu35/supabase-rls-leak-demo.
If you want this checked by hand
I run a fixed-scope read-only review of Supabase RLS and grants: per-table access matrix across anon, authenticated and service_role, write-path checks, and remediation SQL for every finding. See the review scope and pricing.