Skip to content

Supabase's October 30 change: new public tables need a grant for Data API access. What to doSupabase's October 30 change is live: new public tables need a grant for Data API access. What broke and how to fix it

24 tools · 139 fixes · 42 issue guides · Sources: Supabase, GitHub, postgresql.org, Vercel + 53

Issues

How do I check and fix Supabase's October 30, 2026 Data API grants change?

The change is live: Oct 30: what broke, and the 2-minute fix. The full guide to the change, with the fix SQL, is Supabase's Oct 30 change: new tables aren't exposed automatically.

From October 30, 2026, new public tables in existing Supabase projects need an explicit grant. Read-only queries list what is missing; the fix SQL follows.

Check what your anon key can read

This check counts the rows your anon key can see, from your browser. It does not detect the October 30 grant change. For that, run the two read-only queries below.

One name per line, or separated by commas. Copy them from the Table Editor, or from the create table lines in your migrations. The anon key can no longer list every table, so names you paste are what get checked. If the catalog happens to answer, extra names are added after yours.

The anon key, and the newer publishable key, are meant to be public. They only reach what your grants and row level security allow. The secret key and the legacy service_role key are not public. Do not paste those. This field is not submitted to ShipSecurely.

Only test projects you own or are authorized to test. The anon key is never sent to ShipSecurely. It is never sent to our servers. Supabase receives it. We do not receive it, log it, or store it. What this check sends.

Share link · Share on X

By ShipSecurely Editorial · Updated · How we make these guides

Shows up in: Supabase, Postgres

What it is

You are affected if your app uses the Data API and new tables are created in public without a GRANT. From October 30, 2026, existing projects stop exposing such tables: requests fail with 42501, often reported as 401 or 403. Existing tables keep their grants. Grant each role explicitly and keep row level security on.

That is what Supabase's changelog, Tables not exposed to Data and GraphQL API automatically, says for existing projects. The 42501 troubleshooting page says clients often report Postgres 42501 errors as 401 or 403. The fix SQL, the early opt-in, and the rollback are in Supabase's Oct 30 change: new tables aren't exposed automatically. The Supabase exposure checker counts the rows your anon key can see. It does not detect the grant change. Use the queries below to inspect current default privileges and table grants, and to list each table and view that a Data API role is missing a grant on.

The sections below cover what changed and who has to act, the read-only queries, how to tell a missing grant from a policy, a one-table check over HTTP, and the fix SQL. If you only need the fix for one new table with an owner column, Oct 30: what broke, and the 2-minute fix has it with a policy for each operation. Tables are not the only objects to check: Can anyone call your Supabase functions? covers functions, Do Supabase views bypass RLS? covers views, and grants in migrations covers the Supabase CLI, Prisma, and Drizzle. Related guides are grouped under database and row level security, and Supabase advisories are on Supabase incidents.

What changed on October 30, 2026?

The changelog compares the behavior before and after the change, step by step:

  • Create a table in public. Before: "Table is reachable via Data API on creation." After: "Table exists but is not reachable via Data API."
  • Grant to anon, authenticated, and service_role. Before: "Implicit, via default privileges." After: "Required, explicit grant statement."
  • Enable row level security, and create policies. Both "Remains the same."

The changelog adds: "RLS behavior remains unchanged. Grants are a separate layer: they control whether a role can access a table at all, while RLS controls which rows that role can see." Existing tables "keep their current grants and stay reachable."

The changelog dates are: April 28, 2026, when the changelog was published and the setting became an option at project creation; May 30, 2026, when "this setting starts to become the default for all new projects. This will be a gradual rollout over a few weeks to monitor impact."; and October 30, 2026, "when the new behavior is enforced on all existing projects."

Who has to act?

The changelog says you need to act if any of these is true:

  • "Your app reads or writes tables in the public schema via the Data API (PostgREST, GraphQL, supabase-js, any of the client libraries, 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 over the Data API on creation."

It also says "You are unaffected if you only talk to your database over a direct Postgres connection," and it lists self-hosted as not affected. Tables in storage, auth, realtime, and custom schemas keep their current grants, as the FAQ below says.

Has my project already switched?

Run the default privileges query under How do I check if my project is affected? in the SQL editor of a project you own. It is read-only. If it returns no rows, new tables in public already get none of the four privileges the Data API uses (select, insert, update, and delete) automatically, which is the behavior the change produces. If it returns rows, new tables still get those privileges.

A row that lists SELECT, INSERT, UPDATE, or DELETE for anon or authenticated means new tables still give that role those privileges automatically. No rows means new tables give anon and authenticated none of the four until you grant them.

The query reads the project's current default privileges. It does not read Supabase's rollout schedule, and it does not detect that the change was applied. Securing your API says default privileges differ between projects, depending on when the project was created and its Data API settings. A project that already opted in, or where someone revoked the default privileges by hand, also returns no rows. The section it sits in explains what the query keeps and leaves out.

Does the Oct 30 change protect my existing tables? No.

No. The changelog says existing tables keep their current grants and stay reachable. An existing table with row level security off and a select grant to anon can still be read with your publishable key, which is the anon role when there is no session, per the API keys guide. Securing your API says tables and views exposed through the Data API without row level security can be accessed by any role with matching grants. The Row Level Security guide says a table in an exposed schema without row level security is readable and writable by any role with a grant on it, and that adding policies doesn't remove those grants.

What protects an existing table is row level security with a policy for each command you granted, and revoking grants the table should not have. For an existing table you do not want reachable through the Data API, the changelog shows this per-table revoke:

revoke all on table public.your_table from anon, authenticated, service_role;

The Supabase exposure checker counts the rows your anon key can see. It does not detect the grant change. Supabase tables without row level security covers how to find an open table and write its policies.

What about functions?

New functions stay callable by every role. Securing your API says that disabling "Automatically expose new tables" in the Data API settings revokes the privileges the Data API uses, and lists what remains unchanged, including this for functions: "New functions are callable by every role. Postgres grants execute to PUBLIC through a global default, and a revoke limited to one schema can't remove it." The same guide says row level security doesn't apply to functions, so grant execute only to the roles that need to call them. Can anyone call your Supabase functions? covers checking which functions anon can call and revoking execute.

How do I check if my project is affected?

Answer these yourself. Nothing on this page sends anything to ShipSecurely. Run the SQL only in the SQL editor of a project you own.

  1. Does your app use the Data API? That is supabase-js or another Supabase client library, or HTTP to /rest/v1/ or /graphql/v1. The changelog says an app that only uses a direct Postgres connection (psql, an ORM, or an app server with a connection string) is not affected. It also lists self-hosted as not affected.
  2. Will anything create tables in public after the change reaches your project? Migrations, the SQL editor, an AI coding tool, a CLI script, and a Management API call all count. The changelog says the API cannot see a table without a grant, however it was created. If nothing will create a table, your existing tables keep their grants and stay reachable.
  3. Do those steps include GRANT statements? If yes, you are ready. If no, you are affected: the first new table without a grant returns 42501 until you grant it.

What do new tables give anon and authenticated automatically?

This read-only query is adapted from the one in Securing your API, which says default privileges differ between projects, depending on when the project was created and its Data API settings. It keeps only new tables that the postgres role creates in public, the anon and authenticated roles, and the four privileges the Data API uses: select, insert, update, and delete.

select
  a.grantee::regrole as role,
  string_agg(a.privilege_type, ', ' order by a.privilege_type) as data_api_privileges
from pg_default_acl d
cross join lateral aclexplode(d.defaclacl) a
where d.defaclrole = 'postgres'::regrole
  and d.defaclnamespace = 'public'::regnamespace
  and d.defaclobjtype = 'r'
  and a.grantee in ('anon'::regrole, 'authenticated'::regrole)
  and a.privilege_type in ('SELECT', 'INSERT', 'UPDATE', 'DELETE')
group by 1
order by 1;

Each row lists only the privileges that role gets automatically on new tables. Any of the four that a row does not list needs an explicit GRANT. No rows means a new table gives anon and authenticated none of those four privileges until you grant them. The query shows the current default privileges, so read it as what a new table gets today.

The filter matters. Securing your API says that when new tables stop being exposed automatically, they keep truncate, references, trigger, and maintain, and that the Data API doesn't use these privileges. maintain exists on Postgres 17 and later. The unfiltered query in that guide can therefore return rows for anon and authenticated that list only privileges the Data API does not use.

This query covers tables only. It does not show sequences or functions. Securing your API says new functions are callable by every role: "Postgres grants execute to PUBLIC through a global default, and a revoke limited to one schema can't remove it." It also says its own query "leaves out the execute privilege that every new function receives through PUBLIC." No rows here says nothing about functions.

Which public tables lack a grant?

This read-only query lists each table in public, whether row level security is on, and whether anon and authenticated hold the privileges. A false means the role lacks that table-level privilege. It does not mean every request as that role fails: a request that needs only privileges the role does hold still works, and so can one limited to columns with a column-level grant, which this query does not show. A request that needs the missing privilege returns 42501. authenticated_write is true when the role holds any of insert, update, or delete.

select
  tablename as table_name,
  rowsecurity as rls_enabled,
  has_table_privilege('anon', format('%I.%I', schemaname, tablename), 'select') as anon_select,
  has_table_privilege('authenticated', format('%I.%I', schemaname, tablename), 'select') as authenticated_select,
  has_table_privilege('authenticated', format('%I.%I', schemaname, tablename), 'insert, update, delete') as authenticated_write
from pg_tables
where schemaname = 'public'
order by rls_enabled, table_name;

rowsecurity is the pg_tables column that is true when row security is enabled on the table. has_table_privilege reports whether the role holds the privilege, and a comma-separated list is true when any one of them is held. Run the query again after you add a table.

Which Data API grants are missing on each table and view?

The table query above reports select and one write flag, for anon and authenticated only. This read-only query lists each table, partitioned table, and view in public, each Data API role that lacks any of SELECT, INSERT, UPDATE, or DELETE on it, and which of those four that role lacks. Securing your API says grants "determine which Postgres roles can reach a table, view, or function over the Data API. These roles include anon, authenticated, and service_role." Run it in the SQL editor of a project you own.

select
  c.relname as name,
  case c.relkind when 'v' then 'view' else 'table' end as kind,
  r.role,
  string_agg(p.privilege, ', ' order by p.privilege) as missing
from pg_class c
cross join (values ('anon'), ('authenticated'), ('service_role')) as r (role)
cross join (values ('SELECT'), ('INSERT'), ('UPDATE'), ('DELETE')) as p (privilege)
where c.relnamespace = 'public'::regnamespace
  and c.relkind in ('r', 'p', 'v')
  and not has_table_privilege(r.role, c.oid, p.privilege)
group by c.relname, c.relkind, r.role
order by c.relname, r.role;

Each row is one role on one table or view, with the privileges it lacks. No rows means all three roles hold all four privileges on every table, partitioned table and view the query covers. The query uses has_table_privilege, so it reads the privileges each role can actually use, including one granted to PUBLIC. It counts only the four privileges above. It skips materialized views and foreign tables, because it lists only relkind r, p, and v. Securing your API says new tables keep truncate, references, trigger, and maintain, and that "The Data API doesn't use these privileges."

A missing privilege is not a bug by itself. It is a list to compare with what the app does. anon often should have nothing, or select only on data meant to be public, and a view the app only reads needs select alone. Securing your API says to "Grant the minimum privileges each role needs." A missing privilege the app does need is what fails: the Row Level Security guide says "A missing grant raises a 42501 error before any policy runs."

Like the table query, it does not show column-level grants, and it does not check row level security. Use the table query's rls_enabled column for that. The fix SQL for a row the app needs is under How to fix.

Is it a missing grant or a policy?

Postgres checks the grant before row level security, and the two failures look different. The API keys guide says: "Postgres evaluates table grants first, and only then applies Row Level Security. The two failures look different. A missing grant returns a permission error, including for service_role, whereas a policy that matches no rows returns an empty result. Check the grant before you debug the policy." The Row Level Security guide says the same: "When a request fails that your policy should allow, check the grants before you change the policy."

Server code that uses the secret key needs the grant too. The API keys guide says "Policies never apply to a secret key, because service_role has the BYPASSRLS attribute," so a 42501 there is not a policy problem. Check the service_role rows of the query above.

A 42501 can have causes other than a missing table grant. The 42501 troubleshooting page also lists an insert or update that row level security rejects for anon or authenticated, and a column-level restriction, including select *.

How do I check one table over HTTP?

This sends one read to the Data API as anon. With nobody signed in, the publishable key is the anon role, per the API keys guide, which also says to "Send publishable and secret keys on the apikey header, not on Authorization: Bearer." Replace the project URL, the table name, and the key:

curl "https://your-project.supabase.co/rest/v1/your_table?select=*&limit=1" \
  -H "apikey: <publishable key>"
  • A JSON body with "code": "42501" and permission denied for table means anon has no select grant on the table. That is expected when signed-out visitors should not read it.
  • [] means anon has the grant and no row came back: no policy matched a row, or the table is empty. The Row Level Security guide says that once row level security is enabled, no data is accessible through the API with a publishable key until you create policies.
  • Rows mean anon can read them. If the table is not meant to be public, revoke the anon grant or fix the policy.

Use only the publishable key here. Policies never apply to a secret key, so its result would not show what your signed-out visitors see, and the API keys guide says a secret key must never travel "In a request header, until you have log sanitization in place."

The Oct 30 checklist

Three steps from Supabase's docs. Run them in the SQL editor of a project you own, or put them in a migration. This is the checklist the signup box emails to new subscribers.

1. Grant each role what it needs

For each new table in public that the Data API should reach, grant only what each role needs. The changelog says to treat the grant, enabling row level security, and the policies as a unit, and Securing your API says to grant the minimum privileges each role needs. Skip the anon line when signed-out visitors should not reach the table.

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. Fix a 42501

A missing grant returns this JSON from PostgREST. The changelog says the hint shows which role is missing which privilege, along with the GRANT needed to fix it. Clients often report the error as HTTP 401 or 403.

{
  "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 42501 troubleshooting page checks a table's current privileges with this query. Grant the missing privilege only if that role should have it. The same page lists other 42501 causes: the auth or vault schema, a custom schema, a column privilege, and an insert or update that row level security rejects.

select grantee, privilege_type
from information_schema.role_table_grants
where table_name = 'your_table';

Do not clear a 42501 by granting everything to anon while row level security is off. Securing your API says tables exposed through the Data API without row level security can be accessed by any role with matching grants.

3. Check row level security

Grants decide whether a role reaches a table. Row level security decides which rows. Run the table query above. A row with rls_enabled false and a grant true can be accessed by any request with that role. Enable row level security and add a policy per command you granted. Supabase tables without row level security covers how to find an open table and write its policies. This is the changelog's example:

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);

The Row Level Security guide says that once row level security is enabled, no data is accessible through the API with a publishable key until you create policies. It also says adding policies doesn't take grants back.

How to check in 5 minutes

  1. Confirm the app uses the Data API: supabase-js, another client library, /rest/v1/, or /graphql/v1. A direct Postgres connection is outside the change.

  2. Open the migrations, scripts, or AI coding tool prompts that create tables in public. Look for a grant next to each create table.

  3. Run the default privileges query above in the SQL editor of a project you own. Each row lists only the privileges that role gets automatically on new tables. Any of select, insert, update, or delete not listed needs an explicit grant before that role can use it through the Data API. No rows means none of the four is automatic.

  4. Open Security Advisor. The changelog says it flags affected tables per project and shows the remediation SQL through October 30.

  5. After you add a table, call it with the publishable key, as in How do I check one table over HTTP? With no session that key is the anon role, per the API keys guide. Code 42501 with permission denied for table is the missing grant.

How to fix

Supabase

Put the grant, row level security, and the policies in the same migration, for every new table the Data API should reach. For each table the missing-grants query lists that the app needs, replace your_table and run this. It grants authenticated and service_role the four privileges, as in the changelog's example. Row level security comes first; that order is ours, not Supabase's, because the changelog's example lists the grant first and says "Treat these three steps as a unit." The comments are from Securing your API:

alter table public.your_table enable row level security;

-- Full access for signed-in users; RLS still applies
grant select, insert, update, delete on table public.your_table to authenticated;

-- Full access for server-side code using the service role
grant select, insert, update, delete on table public.your_table to service_role;

With row level security on and no policy, the Row Level Security guide says no data is accessible through the API with a publishable key. Add one policy per command you granted. The guide says: "Write a separate policy for select, insert, update, and delete." The 2-minute fix has all four for a table with an owner column, and step 3 of the checklist above has the changelog's select policy.

Only for a table signed-out visitors should read, add a select grant and a select policy for anon. The Row Level Security guide says to use a policy to anon with using ( true ) "only for data that is meant to be public":

grant select on table public.your_table to anon;

create policy "anyone can read"
  on public.your_table for select
  to anon
  using (true);

For a serial column, generating an ID with nextval requires USAGE or UPDATE on its sequence, per PostgreSQL's sequence functions documentation. Securing your API says new sequences keep update, so this change alone does not require an extra sequence grant for serial inserts. A Supabase maintainer's reply in the changelog discussion still recommends the matching sequence grant in the same migration, and it is needed if a role that inserts has neither USAGE nor UPDATE on the sequence. Replace public.your_table_id_seq with the sequence behind your serial column:

grant usage, select on sequence public.your_table_id_seq to authenticated, service_role;

For a view, the Row Level Security guide says "Views bypass RLS by default because they are usually created with the postgres user," and "In Postgres 15 and above, make a view obey the RLS policies of its underlying tables when invoked by anon and authenticated by setting security_invoker = true." Set it before you grant, and grant select only:

alter view public.your_view set (security_invoker = true);
grant select on table public.your_view to authenticated;

Then run the missing-grants query again. authenticated and service_role should no longer be listed for your_table. anon stays listed unless you added the public-read block, and then it lacks only DELETE, INSERT, UPDATE. your_view stays listed after the fix: anon and service_role lack all four privileges, and authenticated lacks DELETE, INSERT, UPDATE, which is expected for a select-only view.

Supabase's Oct 30 change: new tables aren't exposed automatically covers opting in early, the rollback, and function grants.

AI coding tools

The changelog says to update the system prompt of an AI coding tool that creates tables, or to adopt the Supabase agent skill, which it says includes the grants step. Agents can also self-correct from the hint in the 42501 response.

Copy the fix prompt

Paste this into Cursor, Lovable, Bolt, or Claude Code. Read the diff before you merge it.

You are checking one Supabase project for the October 30, 2026 Data API grant change. Do not add features. Do not print row contents.

From October 30, 2026, Supabase applies this to existing projects: a new table in public is not reachable through the Data API or GraphQL until a role is granted access. Existing tables keep their current grants. A missing grant returns code 42501, "permission denied for table". Clients often report it as HTTP 401 or 403.

1. List every place that creates a table in public: migrations, seed scripts, and SQL the app runs.
2. For each table the app reads or writes through supabase-js, /rest/v1/, or /graphql/v1, make sure the same migration grants only the commands each role needs, enables row level security, and adds a policy per granted command.
3. Skip the anon grant when signed-out visitors should not reach the table.
4. Do not grant everything to anon. Do not turn row level security off.

Show the SQL. Then say how to confirm the 42501 is gone for the role that should have access.

Questions

Has my project already switched?

The default privileges query on this page shows what new tables in public get today. No rows means new tables give anon and authenticated none of select, insert, update, or delete until you grant them, which is the behavior the change produces. It does not detect that the change was applied: a project that opted in early, or where someone revoked the default privileges by hand, also returns no rows.

Does the Oct 30 change protect my existing tables?

No. The changelog says existing tables keep their current grants and stay reachable. Securing your API says tables exposed through the Data API without row level security can be accessed by any role with matching grants. Enable row level security with policies, and revoke grants a table should not have.

What about functions?

New functions stay callable by every role. Securing your API says that with "Automatically expose new tables" disabled, new functions are still callable by every role, because Postgres grants execute to PUBLIC through a global default and a revoke limited to one schema can't remove it. Can anyone call your Supabase functions? covers the check and the fix.

Does the Oct 30 change break my existing tables?

No. The changelog says existing tables keep their current grants and stay reachable. Its rollout table says tables without explicit grants stop being reachable on October 30, and the same post says a project with no flagged tables today still breaks the first time someone adds a new one. A new table in public without a grant is what fails.

Does the Supabase exposure checker detect this change?

No. The exposure checker counts the rows your anon key can see, from your browser. It does not read grants, and it reports a 401, 403, or 42501 as inconclusive. Use the queries on this page to inspect current default privileges and table grants. They do not show whether the October 30 change has been applied to your project.

Is a 42501 a 401 or a 403?

The PostgREST body carries code 42501 and permission denied for table. The 42501 troubleshooting page says clients often report Postgres 42501 errors as HTTP 401 or 403.

Does this affect storage, auth, or custom schemas?

The changelog says no. The change touches default privileges in public. Tables in storage, auth, realtime, and custom schemas you expose keep their current grants and defaults.

Do I have to wait for October 30?

No. The changelog shows how to opt in on an existing project today, and how to roll back. Both are in Supabase's Oct 30 change: new tables aren't exposed automatically.

Sources

Related

Need someone to fix it for you? See /help.

The weekly fix

The weekly fix: 3 real security mistakes in vibe-coded apps and how to fix them, every Tuesday.

One email, every Tuesday. A welcome note follows the first signup.