DEV Community

dave thomas
dave thomas

Posted on

Supabase's Oct 30 change: what breaks, what doesn't, and the quick fix to avoid

On October 30, 2026, Supabase applies a new default to all existing projects: new tables in the public schema are not exposed to the Data API automatically [S1]. "Data API" means the auto-generated REST and GraphQL layer, which is what supabase-js and the other client libraries call.
This post covers what changes, who it affects, how to check your own project with SQL, the safe way to expose a table, and the quick fix that can quietly re-expose your data.
What changes
Until now, a Supabase project with default settings granted select, insert, update and delete on every table in public to the anon, authenticated and service_role roles, through Postgres default privileges. Every table you created was reachable through the API the moment it existed [S1].
After the change [S1]:
Step
Before
After
Create a table in public
Reachable through the Data API on creation
Exists, but the Data API can't reach it
Grants to anon / authenticated / service_role
Implicit, via default privileges
You must run an explicit GRANT
enable row level security / create policy
Unchanged
Unchanged
What stays the same [S1]:
Existing tables keep their current grants and stay reachable.
RLS behaves the same. Grants are a separate layer.
Direct Postgres connections (psql, an ORM, or an app server with a connection string) aren't affected.
The change touches default privileges in public only. storage, auth, realtime and custom schemas keep their current grants and defaults.
When a grant is missing, the API returns an error instead of failing silently [S1]:
{
"code": "42501",
"message": "permission denied for table your_table",
"hint": "Grant the required privileges to the current role with: GRANT SELECT ON public.your_table TO anon;"
}
Timeline [S1]:
Apr 28, 2026: changelog published, and the opt-in setting appeared at project creation.
May 30, 2026: the new behavior became the default for new projects, rolled out gradually.
Oct 30, 2026: enforced on all existing projects.
Supabase emails owners and admins of active projects: a five-week notice went out Sep 23 and a final notice goes out Oct 23.
Until Oct 30, Security Advisor flags affected tables and shows the SQL to fix them.
Who is affected
According to Supabase [S1], you need to act if any of these is true:
Your app reads or writes public tables through the Data API (PostgREST, GraphQL, supabase-js or another client library, or direct HTTP to /rest/v1/ or /graphql/v1).
Your migrations or provisioning flow create tables in public without explicit GRANT statements.
Your AI coding tool, CLI script or Management API call creates tables and expects them to be reachable through the API right away.
A project with no flagged tables today still breaks the first time someone adds a new table without a grant [S1].
Supabase's stated reason matters for anyone building with AI tools. Agents, CLI scripts and AI platforms now create tables, often with no human reviewing the diff. The old defaults made it easy to expose a new table before anyone had secured it [S1].
How to check your own project
Start with the built-in tools: Security Advisor and the Data API exposure badge in the Table Editor list the tables worth reviewing [S1].
To see the details yourself, run the queries below in the SQL Editor. They only read the system catalogs and change nothing.

  1. RLS status of every table in public select tablename, rowsecurity as rls_enabled from pg_tables where schemaname = 'public' order by rls_enabled, tablename;
  2. Which API roles hold which privileges on which tables select table_name, grantee, string_agg(privilege_type, ', ' order by privilege_type) as privileges from information_schema.role_table_grants where table_schema = 'public' and grantee in ('anon', 'authenticated', 'service_role') group by table_name, grantee order by table_name, grantee;
  3. The dangerous combination: the API can read the table AND RLS is off select c.relname as table_name, case c.relkind when 'r' then 'table' when 'p' then 'partitioned table' when 'v' then 'view' when 'm' then 'materialized view' end as kind, has_table_privilege('anon', c.oid, 'select') as anon_can_select, has_table_privilege('authenticated', c.oid, 'select') as authenticated_can_select, c.relrowsecurity as rls_enabled 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') and not c.relrowsecurity and (has_table_privilege('anon', c.oid, 'select') or has_table_privilege('authenticated', c.oid, 'select')) order by table_name; Any row here can be read through the API by that role, with no row filtering. For anon, that means anyone holding your public anon key, and that key ships in your frontend JavaScript by design. Views and materialized views show up here too, because RLS isn't enabled on them directly; review them separately.
  4. Your current default privileges (what future tables will get) select pg_get_userbyid(d.defaclrole) as for_role, d.defaclnamespace::regnamespace as schema, case d.defaclobjtype when 'r' then 'tables' when 'S' then 'sequences' when 'f' then 'functions' else d.defaclobjtype::text end as object_type, d.defaclacl as default_acl from pg_default_acl d order by schema, for_role, object_type;
  5. Policies that let everyone through select tablename, policyname, roles, cmd, qual, with_check from pg_policies where schemaname = 'public' and (qual = 'true' or with_check = 'true') order by tablename; A policy of using (true) has RLS turned on but filters no rows. Make sure that's what you intend for each table listed. The safe pattern: minimal grants plus RLS, as one unit Supabase recommends treating three steps as one unit, in the same migration [S1]: -- 1. Grant only the privileges each role needs grant select on public.your_table to anon; -- only if logged-out users should read it grant select, insert, update, delete on public.your_table to authenticated; grant select, insert, update, delete on public.your_table to service_role;

-- 2. Enable RLS
alter table public.your_table enable row level security;

-- 3. Add the policies you need
create policy "users can read their own rows"
on public.your_table
for select
to authenticated
using (auth.uid() = user_id);
These come from the Securing your API guide [S2]:
Grants decide which roles can reach a table; RLS decides which rows they see. Use both.
If anon shouldn't touch a table at all, don't grant it anything. That's stricter than relying on a policy alone.
Tables created in the Dashboard have RLS on by default. Tables created with SQL or by tools may not, so check query 1.
RLS doesn't apply to functions. Grant EXECUTE carefully, and review any SECURITY DEFINER functions you expose.
If your AI coding tool creates tables, update its system prompt (or use Supabase's agent skill) so grants, RLS and policies always ship together [S1]. The 42501 hint is also written so an agent can correct itself when a grant is missing [S1].
The risky quick fix
The Supabase FAQ includes a rollback for people who opted in early and broke their app [S1]. It restores the old default privileges and then grants in bulk:
grant select, insert, update, delete on all tables in schema public to anon, authenticated, service_role;
grant usage, select on all sequences in schema public to anon, authenticated, service_role;
Supabase documents this rollback, and it works. It also returns you to the old behavior, where every table is exposed, including tables you meant to keep private or never protected with RLS. Supabase's own note says to revoke private tables individually afterwards [S1]:
revoke all on table public.your_private_table from anon, authenticated, service_role;
Expect people to copy the bulk grant from a forum or from their AI assistant on Oct 31, because it makes the 42501 errors go away. If you do use it, run queries 3 and 5 immediately afterwards and fix every row they return.
A better approach: grant per table, for the roles that need it, in the same migration that enables RLS and adds policies.
A short checklist before Oct 30
Open Security Advisor and the Table Editor exposure badge [S1].
Run queries 1 and 3. Every table the API can read should have RLS on and real policies.
Add explicit GRANT statements to the migrations or prompts that create tables [S1].
Optional: opt in early on a staging copy using the revoke statements in the changelog, then test your app [S1].
After any bulk grant, run queries 3 and 5 again.
I'm Dave. I built Vibe Greenlight with my AI agent, Eric. It's a launch check for AI-built apps. If you want an outside view of your live app, there's a free mini-scan that checks your homepage for exposed keys and files, with no repo access needed. The paid Launch Check adds a read-only probe for Supabase tables your anon key can read: vibegreenlight.com. It's an automated check, not a pentest, and it doesn't replace running the queries above.
Sources
Supabase changelog: https://supabase.com/changelog/45329-breaking-change-tables-not-exposed-to-data-and-graphql-api-automatically
Supabase docs, Securing your API: https://supabase.com/docs/guides/api/securing-your-api

Top comments (0)