The change at a glance
Your current tables are fine
Anything you’ve already built keeps working after October 30.
New tables start locked
Your app can’t use a new table until you say which roles may access it.
Rebuilds count as new
A database reset, preview branch or fresh project recreates every table, so all of them start locked.
It’s a good change for security: tables are no longer open just because they exist. The main risk is a rushed fix that opens everything up again. This guide shows the safe way.
New tables need a “yes” before your app can use them
Your app talks to Supabase through the Data API. That includes supabase-js, Lovable, Bolt and most AI-built apps. Until now, every new table in the public schema was automatically open to it. From October 30, 2026, a new table stays closed until you add a Postgres GRANT, a one-line permission saying which roles may use it.
- APRIL 28, 2026New projects could opt in early.
- MAY 30, 2026Locked-by-default became the norm for new projects.
- OCTOBER 30, 2026Applies to every existing project, including yours.
Not changing: existing tables, how Row Level Security works, and the storage, auth, realtime and custom schemas.
Find your situation
- You’re fineYour app is done and you won’t add tables.
Existing tables keep their access. Nothing breaks.
- You’re fineYour server only uses a direct database connection.
ORMs,
psqland connection strings aren’t affected, according to Supabase. - ActionYou’ll build a feature that adds a table.
Grant access to the new table, or the feature won’t load.
- ActionYou rebuild from migrations.
supabase db reset, preview branches, fresh projects and client handovers recreate every table. Without grants in your migrations, the rebuilt app can’t read its own data. - CheckAn AI tool, script or template creates your tables.
It may still expect new tables to work right away. Make sure its migrations include grants.
A new feature suddenly says “permission denied”
In the app, a new page loads empty, shows a generic error, or won’t save. In the browser console or logs you’ll see:
Supabase adds a hint showing the GRANT it expects. This is not a Row Level Security problem. RLS usually returns an empty list; a missing grant blocks the whole table. Check the code before you start changing policies.
Fix it in three steps
Decide who should use the table
Is it private (notes, orders, messages) or public (a product catalog, public posts)? That decides which roles get access.
ILLUSTRATIVE EXAMPLE · PICK ONE PER TABLEPrivate data
Signed-in users only. Each user sees just their own rows.
authenticatedPublic data
Anyone may read; changes stay restricted.
anon: selectGranting anonto a private table lets anyone with your public key try to read it. Only RLS would stand in the way.Grant access in the same migration that creates the table
Keep the grant next to
create table, so every rebuild gets it too.MIGRATION · PRIVATE PER-USER TABLE
create table public.your_table ( id uuid primary key default gen_random_uuid(), user_id uuid not null references auth.users (id), body text ); grant select, insert, update, delete on public.your_table to authenticated; grant select, insert, update, delete on public.your_table to service_role; -- No anon grant: signed-out visitors can't reach this table.Turn on RLS and add a policy for each action
The grant says what a role may try. RLS decides which rows it can touch. You need both.
SAME MIGRATION · ROW RULES
alter table public.your_table enable row level security; create policy "Users read their own rows" on public.your_table for select to authenticated using ((select auth.uid()) = user_id);This adds a read rule only. Saving, editing and deleting stay blocked until you add a policy for each. Supabase’s own example also grants
selecttoanon; only do that if signed-out reads are intended.
✓ Do
Grant each new table to only the roles that need it, in its migration, with RLS and policies alongside.
✗ Don’t
Paste grant all on all tables in schema public to anon, or run Supabase’s full rollback to make errors go away.
revoke all on table public.your_private_table from anon, authenticated, service_role;Give it this rule up front
If Lovable, Bolt, Cursor or another assistant writes your database changes, paste this in before asking for new features. Adjust the roles to your app.
From October 30, 2026, new Supabase tables in the public schema are not exposed to the Data API automatically. For every new table you create: 1. In the same migration, add explicit GRANT statements for only the roles that need it. Private user data: authenticated (and service_role). Grant anon only if signed-out visitors should read it. 2. Enable Row Level Security and add a policy for each operation the app uses. 3. Never use "grant all on all tables in schema public" or restore the old default privileges to fix a permission error. 4. If you see error 42501 "permission denied for table", add the specific grant for that table and role, then explain what it allows.
Tip: click once inside the box to select the whole prompt. Afterwards, ask the assistant to show you the grants and policies it added, and read them before applying.
A quick before-and-after checklist
- Open Supabase’s Security Advisor. It flags affected tables and suggests SQL.
- Look for the Data API exposure badge in the Table Editor.
- No private table is granted to
anon. - Every table your app can reach has RLS turned on.
- Signed out, a private table gives a permission error.
- User A sees their own row, but not User B’s.
- After a
supabase db resetor preview branch, everything above still holds.
For the technical person: two read-only SQL checks
Run these in a project you own. Who can access each table:
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;
Which tables have RLS turned on:
select c.relname as table_name, 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 = 'r'
order by c.relname;
Then confirm with real signed-in and signed-out app sessions, not the SQL editor. The Supabase RLS guide walks through the two-account test.
What StackSnack checks—and what it doesn’t
Start with a website scan of public code, known dependency signals and security headers. Website verification unlocks deeper exposed-file checks. Separately approved backend checks can attempt bounded anonymous reads and report masked evidence; they do not persist returned database rows. A URL scan does not read your migrations, list your grants, or certify that every table is configured correctly.
Source: Supabase, “Breaking Change: Tables not exposed to Data and GraphQL API automatically”. Supabase’s changelog is the authority on dates and behavior. Continue with the Supabase RLS guide or launch checklist. StackSnack is independent of Supabase.