On October 30, 2026, Supabase stops auto-granting anon, authenticated, and service_role access to newly created tables in existing projects (changelog 45329). Any table created after that date, in a migration or from the SQL editor, will return 42501 permission denied from the Data API until an explicit GRANT is added.
Update: I wrote the exact copy-paste fix for the errors this change produces — "Supabase permission denied After October 30: The Exact Fix (Copy-Paste SQL)" — including the 60-second check query and GRANT templates for read-only, client-write, and service-role-only apps.
If your app creates tables at runtime — or your team runs migrations late — your users find this error before you do. This guide gets you ready in 30 minutes.
The 5-minute audit: how exposed are you?
Run this in your Supabase SQL editor today:
-- Which tables currently rely on auto-grants?
SELECT c.relname, c.relrowsecurity,
has_table_privilege('anon', c.oid, 'SELECT') as anon_select,
has_table_privilege('authenticated', c.oid, 'SELECT') as auth_select
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'public' AND c.relkind = 'r'
ORDER BY c.relname;
If anon_select or auth_select is true for tables you care about, those grants came from the auto-grant system — and new tables after Oct 30 won't get them.
The 3 things that break
1. Migrations that create tables
If your migration files have CREATE TABLE without a matching GRANT, those tables will be invisible to the API after Oct 30.
Fix (in the migration, right after CREATE TABLE):
CREATE TABLE public.my_table (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
data text
);
-- Add this immediately (the part that was automatic before):
GRANT SELECT, INSERT, UPDATE, DELETE ON public.my_table TO authenticated;
GRANT USAGE ON SEQUENCE public.my_table_id_seq TO authenticated; -- if using serial columns
2. Runtime table creation
Tenant-per-table schemes, admin "add table" flows, and any code that calls CREATE TABLE at runtime. These are the most dangerous — they break silently until a user hits the new table.
Fix (in the function that creates tables):
CREATE OR REPLACE FUNCTION create_tenant_table(tenant_id TEXT)
RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$
BEGIN
EXECUTE 'CREATE TABLE tenant_' || tenant_id || ' (id uuid PRIMARY KEY, data text)';
EXECUTE 'GRANT SELECT, INSERT, UPDATE, DELETE ON tenant_' || tenant_id || ' TO authenticated';
END;
$$;
3. ALTER DEFAULT PRIVILEGES in migrations
You might be tempted to fix this with default privileges. On hosted Supabase, this doesn't work — the postgres role can't alter default privileges for Supabase-managed roles (42501 permission denied). Don't burn an afternoon on this path.
The GRANT trap
When adding GRANT statements, don't over-grant. The most common mistake I see in audits:
-- BAD: this re-opens the exposure the change was designed to prevent
GRANT ALL ON public.my_table TO anon, authenticated;
Instead:
-- GOOD: explicit privileges per role
GRANT SELECT, INSERT, UPDATE, DELETE ON public.my_table TO authenticated;
-- Only add anon grants if the table is genuinely public (marketing content, public profiles)
The 30-minute checklist
- Run the audit query (above) — inventory your current grants (2 min)
- Search migrations for CREATE TABLE — every file that creates a table without a GRANT (5 min)
- Add GRANT statements to each migration (10 min)
- Grep for runtime CREATE TABLE — in functions, triggers, or application code (5 min)
- Test in a staging project — create a table via migration and verify the Data API can access it (5 min)
- Document the change for your team — especially if different people write migrations (3 min)
What Supabase will NOT do for you
- They won't auto-add grants retroactively after the change
- They won't warn you when a new table is created without grants
- They won't provide a Dashboard workflow for managing default privileges
- The
postgresrole can'tALTER DEFAULT PRIVIGESfor their managed roles
Automate the check
Run this monthly as a safety net:
-- Tables that are accessible to authenticated
SELECT relname FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'public' AND c.relkind = 'r'
AND has_table_privilege('authenticated', c.oid, 'SELECT');
Or use the free client-side checker at rls.cenkkurtoglu.com — it scans your public repos for exactly these patterns (migrations without grants, committed secrets, permissive RLS policies) entirely in your browser, nothing stored.
This change is a good thing — it closes a real security exposure (tables accidentally exposed to the anon key). But it's a breaking change, and the teams it hits hardest are the ones running migrations after October 30 without testing. Don't be that team.
I audit Supabase/Postgres setups for production apps — if you'd rather have someone run this entire preparation and hand you a readiness report, that's what I do. The checklist above works on its own either way.
Ready-to-use templates: Supabase October 30 Migration Kit ($29) — 5 migration patterns, audit script, testing script, 15-minute checklist.
Related: if your baselines and dumps re-create Supabase's most dangerous default — every new table born granted to anon — I wrote up the catalog-driven, self-healing fix here: Supabase's Default Privileges Expose Every New Table. Here's the Self-Healing Fix.
Top comments (0)