Guide · Supabase security

Supabase's 30 October 2026 change: new tables need an explicit GRANT

Updated · Rotomal

Supabase scheduled 30 October 2026 as the date existing projects stop automatically granting the anon, authenticated and service_role roles access to new tables in the public schema; its changelog showed no postponement when we checked on 3 November 2026. A table created after the change reaches your project is not reachable through supabase-js, REST or GraphQL until you add GRANT statements; REST calls return "permission denied for table" (code 42501). Existing tables keep their current grants, and Row Level Security works exactly as before. New projects have had this behaviour by default since a gradual rollout that began on 30 May 2026.

Timeline

DateWhat changes
28 April 2026New projects can opt out of automatic exposure when they are created
18 May 2026pg_graphql no longer enabled by default on new projects (a related change)
30 May 2026Opt-out becomes the default for all new projects, rolled out over a few weeks
23 September 2026Five-week notice email to owners of existing projects
23 October 2026Final notice email, one week before the change
30 October 2026Scheduled date for enforcing the new behaviour on all existing projects (no postponement in the changelog on 3 November 2026; confirm on your project with the Table Editor badge)

Who is affected

  • Apps that use the Data API: supabase-js, REST, GraphQL, or direct HTTP calls to /rest/v1/ or /graphql/v1.
  • Migrations that create tables in public without GRANT statements.
  • AI builders, CLI scripts and Management API calls that expect new tables to be exposed automatically.
  • Not affected: apps that connect to Postgres directly, tables that already exist, and tables in the storage, auth, realtime and custom schemas, which keep their current grants.

The error you will see

When a role has no grant on a table, PostgREST returns a clear error rather than failing silently:

{
  "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;"
}

The fix: grant, enable RLS, add policies

This is Supabase's own three-step pattern for each new table. Replace your_table, and grant only what each role needs:

-- 1. Grant the privileges the role needs
grant select on public.your_table to anon;
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);

Read the example carefully before pasting it. It gives anon select on the table, which only makes sense for data logged-out visitors should see. For a private table, leave out the anon line, or revoke it with: revoke all on table public.your_private_table from anon, authenticated, service_role; and grant back only what you need.

RLS does not change, and a GRANT is not protection

Supabase says RLS behaviour remains unchanged. Grants decide whether a role can reach a table at all; RLS decides which rows that role can see. You still need both.

The risk is the hurried fix. Supabase's RLS guide warns that a table in an exposed schema without RLS is readable and writable by any role with a grant on it. Adding grant select, insert, update, delete ... to anon on a table without RLS turns a 42501 error into an open table.

Check your project

  • Table Editor shows a Data API exposure badge on each table.
  • The Security Advisor flags affected tables and shows remediation SQL.
  • To adopt the new behaviour early, Supabase gives alter default privileges statements that revoke default grants on future tables and sequences in public.
  • For projects that opted in early, Supabase also documents rollback statements that restore the old defaults for future tables; use them only if you understand that every new table will be exposed again.
-- Supabase's opt-in for existing projects (future tables only)
alter default privileges for role postgres in schema public
  revoke select, insert, update, delete on tables from anon, authenticated, service_role;

alter default privileges for role postgres in schema public
  revoke usage, select on sequences from anon, authenticated, service_role;

Where Launch Lock helps

The change itself is safe: it makes exposure an explicit decision. What goes wrong is the fix applied under pressure, such as a builder prompt that grants everything to anon. Launch Lock tests every table of your live app from the outside with the public key and two test accounts, so you see which tables anonymous visitors and other users can read or write after your grants, with the corrected SQL for each finding.

Questions

Did my existing app break on 30 October 2026?

Tables that already existed keep their grants, so a stable app keeps working. Breakage shows up when something creates a new table in public, such as a migration, an AI builder or a script, and the app then tries to read it through the Data API. Our guide to the "permission denied for table" error covers that and the other causes of 42501.

Do I need to change my RLS policies?

No. Supabase says RLS behaviour is unchanged. You add GRANT statements for new tables; your existing policies keep doing what they did.

Does this affect Storage, Auth or Realtime?

No. Tables in the storage, auth, realtime and custom schemas keep their current grants.

Why does my builder show permission denied after adding a table?

If the generated migration creates the table without GRANT statements, the Data API refuses access with code 42501. Add the grants each role needs, enable RLS and add policies, as in the pattern above.

Can I go back to automatic exposure?

For projects that opted in early, Supabase's changelog gives rollback statements that restore default grants for future tables and grant access on all existing tables in public. Doing so exposes every new table again, so keep RLS on every table if you use them.

Sources

Guides and answers