🚀 New: chi (χ) — an open-source autoresearch harness for fleets of LLM coding agents. Read the announcement.

Supabase's Grant Change Hits Existing Projects Today. Granting the Table Is Not Enough for serial Columns

From 30 October 2026 Supabase stops granting Data API roles access to new public tables on existing projects. The changelog example grants tables only, and the opt-in revokes sequence privileges too. A local Postgres 16 test shows where a serial insert still fails.

Contents

Supabase’s changelog entry says that on 30 October 2026 the “no automatic grants” setting is applied to all existing projects. It has been the default for new projects since 30 May. After it reaches your project, a table you create in public is no longer granted to anon, authenticated or service_role by default privileges, so the Data API cannot see it until a migration grants access.

Existing tables keep their grants. Direct Postgres connections are unaffected. The people who get hit are teams whose migrations create a table and rely on the platform to make it reachable, and the first failure usually shows up a week from now in a migration nobody was thinking about.

What the changelog gives you, and what it leaves out

The recommended pattern in the changelog is three steps treated as a unit: grant, enable RLS, add policies. The grant step reads:

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;

Those are table privileges. The opt-in snippet for existing projects, the one that mirrors what the dashboard does at project creation, revokes more than tables. It also runs:

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

So after the change, a new sequence in public is also not usable by those roles. The changelog says the sequence revoke is part of what the setting does. It does not show a matching sequence grant in its example, and I could not find one in the page text I read.

Reproducing the gap

I ran this against a local Postgres 16.15, not a Supabase project. It reproduces the privilege semantics, not the platform. The three roles were created by hand, and the two default-privilege revokes are copied from the changelog.

create table public.a (id serial primary key, v text);
create table public.b (id bigint generated always as identity primary key, v text);
grant select, insert on public.a, public.b to authenticated;

set role authenticated;
insert into public.b(v) values ('x');  -- INSERT 0 1
insert into public.a(v) values ('x');  -- ERROR: permission denied for sequence a_id_seq

The identity column works and the serial column does not, with the table grant identical on both. A serial column is a nextval('a_id_seq') default, and evaluating the default requires USAGE on the sequence for the inserting role. An identity column draws from an internal sequence, and the insert succeeded without any sequence grant in this test.

The fix is one more statement, which I also ran:

grant usage on sequence public.a_id_seq to authenticated;

After it, the insert succeeds and \dp shows authenticated=U/postgres on the sequence.

This matters because serial and bigserial are still all over older schemas, code generators and ORM migration output. A table you copy from an existing migration will carry the pattern forward. The failure is also easy to misread. The changelog shows PostgREST returning 42501 with a hint naming a missing GRANT SELECT ON public.your_table TO anon. Whether the hint for a sequence failure names the sequence is something I did not test, so check what your client actually receives.

Two other places the change does not reach

The grant behavior is controlled by default privileges for the postgres role. Supabase’s troubleshooting article says the supabase_admin role has its own default privileges in public, that postgres cannot change them, and that objects created by supabase_admin still get API-role grants. It names extensions installed in public as the common source, since Supabase creates most extensions as supabase_admin.

The suggested remedy is to install extensions into a dedicated schema, for example create extension if not exists pg_trgm with schema extensions;, and to move relocatable ones with alter extension <name> set schema extensions;. Check search_path on functions and any public.-prefixed calls before moving anything. The article says non-relocatable extensions such as PostGIS need Supabase Support.

The second place is any pipeline that rebuilds a database from schema files. Existing live tables keep their grants, so production looks fine. A database rebuilt later from migrations that never contained GRANT statements has nothing to restore them. That is the real cost of the old default: the privileges lived in the platform, not in your repository.

What I would change in a migration repo

  • Put the grants in the same migration as create table, including grant usage on sequence for every serial or bigserial column and for any sequence the API roles call nextval on directly.
  • For new tables, prefer generated always as identity over serial. In my local test it needed no sequence grant. Converting existing tables is a separate migration with its own risk, so I would not do it as part of this change.
  • Run migrations against a project or local stack created with the new default and exercise the insert path with the authenticated role. A test that only checks the table exists will pass.
  • Query information_schema.role_table_grants for the three roles to list what the Data API can currently reach. That view covers tables, not sequences. For sequences, use \dp or pg_class.relacl.

The changelog gives a defensible reason for the change: access becomes reviewable, diffable and greppable, and a forgotten table is no longer exposed by accident. That reasoning only works if the grants are complete, and for sequence-backed ids the documented example is not.

Sources