VibeRaven
Will the Supabase October 30 Data API change break my AI-built app?
Not the tables you already have. Supabase says existing tables keep their grants and stay reachable. Tables created after the change reaches your project each need an explicit grant before supabase-js can read or write them; without one the call fails with 42501, "permission denied for table". Supabase announced October 30, 2026 for existing projects, but a Supabase CLI pull request merged in August says the earlier default change for new projects "never landed", so treat the date as announced, not confirmed. Either way the fix is the same: grant each new table in the migration that creates it, next to its row level security and policies. Old migrations replayed into a new project or a fresh local database with the new default fail the same way, because they never had to grant anything.
Why this matters
Until now, a table created in the public schema was granted to anon, authenticated and service_role the moment it existed, so a table an agent made was reachable through the API right away, with or without row level security. Supabase's announcement moves that to an explicit grant: the default for new projects from May 30, 2026, rolled out gradually, and for all existing projects from October 30, 2026. Grants decide whether a role can reach a table at all; row level security decides which rows it sees, and Supabase says that part does not change. Only the Data API is affected, which is what supabase-js, REST and GraphQL calls use; an app that talks to Postgres over a direct connection string is not. In an AI-built app the break shows up in two places: the first table a prompt adds after the switch, and old migrations replayed into a new project or a fresh local database that has the new default. Both look like an app that worked yesterday and now cannot read its own data.
What we do not know yet
- Whether October 30 happens on schedule. Supabase's announcement, last edited 2026-05-27, still gives that date for existing projects. A Supabase CLI pull request merged on 2026-08-26 says the cloud default change for new projects "never landed" and that platform projects "still auto-expose new entities". As of 2026-10-01, we found no later Supabase statement that settles it. Check Supabase's changelog on October 30.
- Whether auto_expose_new_tables stays in the CLI. In June, Supabase staff called it a temporary aid scheduled for removal on 2026-10-30. The August pull request removed that date from the TypeScript CLI, its init template, the config schema and the db start notes. As of 2026-10-01, the Go CLI sources on Supabase's develop branch still give it, in the comment in apps/cli-go/pkg/config/api.go, the Go config template and a deprecation warning.
- Whether Supabase Branching accepts that key now. One user reported in June 2026 that a branch build failed to parse config.toml with it. Commenters suggest removing the key and running the announcement's opt-in revoke block from a migration instead, placed after any baseline made with supabase db pull (see the default privileges step below).
- Whether a legacy service_role key is refused in the browser the way a new secret key is. The API keys guide documents the 401 for secret keys only.
- Whether a hosted project returns exactly what this page describes. No hosted project was tested for this page. On 2026-10-01 we ran a local stack with Supabase CLI v2.119.0 (Postgres 17.11.0.002, PostgREST v16.4) and auto_expose_new_tables = false in supabase/config.toml. There, a signed-in read of a new table with row level security and a policy but no grant returned 403 with code 42501, "permission denied for table notes", and the hint "Grant the required privileges to the current role with: GRANT SELECT ON public.notes TO authenticated;". Signed out, the same read returned 401. A read of a granted table whose policy reads an ungranted team_members table returned "permission denied for table team_members", and a security invoker view with no grant returned "permission denied for view project_names". With the key left out, the same CLI version granted new tables and functions to anon and authenticated, the old behavior. The other error text on this page is quoted from Supabase's announcement and docs.
What to check before and after October 30
- Leave the tables you have alone. Supabase staff confirmed in the announcement thread that existing tables on existing projects keep their current grants and are not revoked by the October 30 rollout.
- See which roles can reach a table today, with the query from Supabase's 42501 troubleshooting page:
select grantee, privilege_type from information_schema.role_table_grants where table_name = 'your_table'; - In every migration that creates a table the app reaches, through supabase-js or through a policy, view or function that reads it as the caller, enable row level security, add policies and grant it, in that one migration. Supabase calls the three steps a unit, because Postgres rejects a query with no grant before any policy runs. Grant anon only on tables signed-out visitors read. If the table has a serial or identity column, Supabase staff suggest in the thread that you also grant usage on its sequence. On our local stack (Supabase CLI v2.119.0, auto_expose_new_tables = false) a signed-in insert into a serial table worked without that grant: the CLI's revoke, like the one in the announcement, takes usage and select on new sequences but leaves update, and Postgres accepts update for nextval. With every privilege on the sequences revoked, the serial insert failed with "permission denied for sequence items_serial_id_seq" and an identity insert still worked, as two other commenters in the thread say. No hosted project was tested, so grant usage on a serial column's sequence anyway. Adapted from Supabase's announcement, with row level security first and the anon policy and grant left as comments:
alter table public.your_table enable row level security; create policy "users can read their own rows" on public.your_table for select to authenticated using (auth.uid() = user_id); grant select, insert, update, delete on public.your_table to authenticated; grant select, insert, update, delete on public.your_table to service_role; -- only if signed-out visitors read this table. The policy above is for -- authenticated only, so anon needs its own policy as well as the grant, -- here limited to rows marked public: -- create policy "visitors can read public rows" -- on public.your_table -- for select -- to anon -- using (is_public); -- grant select on public.your_table to anon; - If a coding agent writes your migrations, put that rule in its instructions: every create table in public ships with row level security, policies and its grants in the same migration, including a table only a policy reads. Supabase's announcement suggests updating the agent's system prompt or using the Supabase agent skill, which includes the grants step.
- Read the 42501 message before you fix anything. "permission denied for table your_table" is a missing grant, and Supabase's hint names the GRANT to add. The table it names can differ from the one you queried: Postgres runs a policy with the privileges of the role making the query, so when a policy reads a table that role has no grant on, the error names that table. A blocked insert or update under row level security also returns 42501, with a different message, and clients often report both as a 401 or 403.
- Do not silence 42501 by granting a table that has RLS off. Supabase's docs say a table exposed through the Data API without RLS can be accessed by any role with matching grants, so a grant to anon gives every row to anyone with the key your frontend ships. Enable RLS and add policies first. Views count as tables here: Postgres default privileges for tables also cover views, so a new view needs its own grant. A view runs with its owner's rights unless it is created with (security_invoker = on), which is why Supabase's RLS guide says views bypass RLS by default. The same guide says a view over a protected table hands out every row its policies were meant to withhold. Create views with (security_invoker = on) before you grant them.
- Do not move the secret key into the browser to make the error go away. It bypasses every RLS policy, and it does not fix a missing grant: Supabase's API keys guide says a missing grant returns a permission error for service_role too. The guide lists a 401 for a secret key sent from a browser as a check the new keys add over the legacy service_role key, so do not count on that block for the old key.
- Treat the announcement's rollback as a last resort. Its bulk grant gives anon, authenticated and service_role select, insert, update and delete on all tables in schema public, including any without RLS. If you run it, list the tables without RLS first (the related guides below show a query and tools for that). The announcement itself says to revoke private tables one by one right after.
- If you build new projects from your migrations (one per client, staging, a fresh local database), add one migration after the last create table that grants each existing table the app reaches, through supabase-js or through a policy, view or function that reads it as the caller. Supabase staff confirmed in the thread that a reset with the new default leaves tables from older migrations without grants. Naming each table keeps private ones closed; a bulk grant opens them all.
- Search your migrations for default privilege statements. A grant there turns automatic grants back on for tables created after it, so a local reset can pass while a project with the new default fails. A commenter in the announcement thread says a baseline made with supabase db pull probably contains one:
grep -rni "alter default privileges" supabase/migrations - Test against the new behavior locally. With Supabase CLI v2.102.0 or later, add this to supabase/config.toml, run supabase db reset and use the app. Depending on your Supabase CLI version, leaving the key out can mean either behavior. A CLI pull request merged on 2026-06-10 made an unset key revoke the grants, in releases v2.106.0 through v2.115.0. The one merged on 2026-08-26 switched it back to auto-expose from v2.116.0, and on v2.119.0 our local stack with the key left out still granted new tables to anon and authenticated. So set it to false explicitly:
[api] auto_expose_new_tables = false - Do not expect the change to close new functions. Postgres grants EXECUTE on every new function to PUBLIC, which includes every role, and its docs say a per-schema revoke cannot take a global default away. Supabase staff said in the thread that this is not handled on purpose. Revoke EXECUTE from public, anon and authenticated on each function only your server should call. Supabase's Securing your API page says new functions on existing projects also get EXECUTE for anon, authenticated and service_role, so a revoke from public and anon alone leaves any signed-in user able to call it.
- Do not read a quiet Security Advisor as proof that reads are scoped. Supabase's Advisors docs list using (true) on a read policy as a pattern lint 0024 detects, but the splinter source for that lint skips select policies with using (true) on purpose, because public reads are often deliberate. This page did not run the hosted Advisor, so we do not know which of the two your dashboard follows. Check a 42501 fix that adds a grant and a using (true) read policy yourself rather than waiting for the Advisor to flag it.
Optional repo check
For AI-built Vercel + Supabase apps, `npx -y viberaven@1.6.1 check` reads the repo for both sides of this change. `data_api_grant_without_rls` (critical) fires when a migration grants a public table to anon or authenticated while row level security is off on it, and that includes the bulk grant in the announcement's rollback. `data_api_grant_missing` (warning) fires only when the repo opts in, through `auto_expose_new_tables = false` under `[api]` in `supabase/config.toml` or a migration that revokes the default grants before the table is created. Even then it needs a public table with RLS on, a policy that lets anon or authenticated run the operation, a `.from('table')` call outside server-only files, and no migration granting it. On a small fixture repo on 2026-10-01 it printed `public.notes is used from the client (src/app/notes/page.tsx:8) for select, but authenticated has no select grant on it` and gave the fix `grant select on table public.notes to authenticated;`. With the config line removed, the same repo got no `data_api_grant_missing` warning, while its critical `data_api_grant_without_rls` finding on a second table, granted to anon with RLS off, stayed. It does not read the grants your hosted project holds, see tables made in the dashboard, or look at grants for sequences or service_role, and it skips Edge Functions, so a table only your server reaches with the secret key is not flagged. A table read only inside a policy is not flagged either, so a missing grant on a lookup table such as team_members goes unreported. It also does not tell views from tables: it can treat a view as a table, including suggesting row level security for it, which does not apply to a view, and it does not flag a view the client queries with no grant. It is advice, not a gate, and a repository check, not a live database or security test. A clean result does not prove the app is secure. It exits with code 1 when it finds a blocker and writes a `.viberaven` folder that it does not add to `.gitignore`.
Related guides
Sources
- Supabase, Changelog: Tables not exposed to Data and GraphQL API automatically
- Supabase, Announcement thread and staff answers (discussion #45329)
- Supabase CLI, pull request #6337
- Supabase CLI, pull request #5524
- Supabase CLI, apps/cli-go/pkg/config/api.go on develop
- Supabase, Securing your API
- Supabase, Database API 42501 errors
- Supabase, Row Level Security
- Supabase, API keys
- Supabase, Advisors
- splinter, lint 0024 (always-true policies)
- Supabase CLI, db reset
- PostgreSQL, Privileges
- PostgreSQL, GRANT
- PostgreSQL, ALTER DEFAULT PRIVILEGES
- PostgreSQL, Row Security Policies
- PostgreSQL, CREATE VIEW
- PostgreSQL, Sequence Manipulation Functions
Do I need to do anything if I never add another table?
Not for the tables you have. Supabase staff confirmed that existing tables on existing projects keep their grants. You need explicit grants when you create a new table in public after the change reaches your project, or when you replay your migrations into a new project or a fresh local database that has the new default.
Does it affect apps that connect to Postgres directly?
No. Supabase says the change applies to the Data API, which supabase-js, REST and GraphQL use. An app that reads and writes over a direct connection string, through psql, an ORM or its own server, is not affected.
Will my RLS policies change?
No. Supabase says row level security behavior stays the same. A grant decides whether a role can reach a table at all; a policy decides which rows it sees. But Postgres runs a policy with the privileges of the role making the query, so any table a policy reads needs a select grant for that role too, or has to be read through a security definer function, which Supabase's RLS guide says to keep out of exposed schemas. A new lookup table such as team_members, read by a policy on an older table, breaks reads on the older table until it is granted.
Does it touch tables in storage, auth or my own schemas?
No. The announcement says tables in storage, auth, realtime and custom schemas keep their current grants and defaults.