---
title: "Supabase, 30 de outubro: o que falha com os novos grants"
description: "A 30 de outubro de 2026 o Supabase deixa de dar grants às tabelas novas de public nos projetos existentes. O que falha, o que continua e quatro verificações."
url: "https://madsalt.tech/pt/blog/supabase-october-30-grant-change/"
author: "Denys Zaichenko"
language: "pt"
published: 2026-10-26
---

# Supabase, 30 de outubro: o que falha com os novos grants

Em resumo

A 30 de outubro de 2026, o Supabase deixa de dar grants automáticos a `anon`, `authenticated` e `service_role` nas tabelas novas de `public` em todos os projetos existentes. As tabelas que já tem continuam a funcionar. Cada tabela criada depois responde `42501 permission denied` ao `supabase-js` e à sua secret key até uma migration lhe dar o grant. Reproduzi a mudança em local: tabelas novas, views novas e tabelas antigas que uma migration apaga e recria falham todas; tabelas existentes, funções RPC e inserts em `serial` continuam a funcionar. Verifique com duas queries SQL e um grep, abaixo, e corrija com grant, RLS e política na mesma migration, nunca com `grant all on all tables`.

Tenho um [verificador de segurança do Supabase](https://madsalt.tech/pt/tools/supabase-security-check/) gratuito que lê os grants de todas as tabelas expostas, por isso quis ver exatamente o que esta mudança faz antes de as pessoas começarem a tropeçar nela. Montei uma stack local do Supabase, dei-lhe os defaults antigos, apliquei a mudança como o Supabase a descreve e fiz um pedido a cada tipo de objeto em que consegui pensar. Este artigo é o que veio de volta e como verificar o seu projeto antes do dia 30.

## O que muda a 30 de outubro de 2026?

Até agora, cada tabela criada no schema `public` recebia `select`, `insert`, `update` e `delete` para `anon`, `authenticated` e `service_role` através dos default privileges do Postgres. É por isso que uma tabela nova ficava acessível a partir do browser no instante em que existia, protegida por RLS ou não. O [aviso de breaking change](https://supabase.com/changelog/45329-breaking-change-tables-not-exposed-to-data-and-graphql-api-automatically) do Supabase retira esses defaults em três passos:

| Data                  | O que acontece                                                                                              |
| --------------------- | ----------------------------------------------------------------------------------------------------------- |
| 28 de abril de 2026   | Os projetos novos podem desativar "Automatically expose new tables" na criação                              |
| 30 de maio de 2026    | O novo comportamento passa a ser o default nos projetos novos, em implementação ao longo de algumas semanas |
| 30 de outubro de 2026 | O novo comportamento é aplicado a todos os projetos existentes                                              |

No seu projeto, a mudança são duas instruções, as mesmas que o Supabase indica para quem quer aderir mais cedo:

```sql
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;
```

Os default privileges só se aplicam a objetos criados depois. Os grants que já existem não são tocados, e é por isso que as suas tabelas atuais continuam a funcionar. O que não é afetado, segundo o aviso: os outros schemas (`auth`, `storage`, `realtime`, os seus), as apps que falam com o Postgres por ligação direta (psql, um ORM, um servidor com connection string) e o próprio RLS. A mudança diz respeito à Data API: REST, GraphQL e todas as bibliotecas cliente construídas sobre ela.

## O que falha e o que continua a funcionar?

O meu setup: Supabase CLI 2.109.1, Postgres 17.6, PostgREST 14.14, a 26 de outubro de 2026. Pus `auto_expose_new_tables = true` no `config.toml` para obter os defaults antigos, criei uma tabela com duas linhas, RLS ligado e uma política de leitura, e depois corri as duas instruções acima. A seguir criei objetos novos e chamei cada um com a chave pública local (`anon`) e com a secret key (`service_role`), um pedido cada.

![O que deixa de responder depois da mudança de 30 de outubro. Supabase local, objetos criados depois do revoke do changelog, chave pública salvo indicação. Tabela nova com RLS e uma política: create table → política, sem grant → 42501, HTTP 401; A mesma tabela com a secret key: create table → política, sem grant → 42501, HTTP 403; View nova sobre uma tabela que ainda funciona: create view → 42501, HTTP 401, sem hint; Tabela antiga apagada e recriada por uma migration: drop + create table → grants perdidos → 42501, HTTP 401.](https://madsalt.tech/charts/pt/supabase-october-30-grant-change.svg)

*A minha reprodução numa stack local do Supabase (CLI 2.109.1, Postgres 17.6, PostgREST 14.14), 26 de outubro de 2026.*

![O que continua a funcionar depois da mudança de 30 de outubro. Mesma stack local, mesmo revoke, chave pública. Tabela criada antes da mudança: tabela existente → grants mantidos → 2 linhas, HTTP 200; Função nova chamada por RPC: create function → execute via PUBLIC → HTTP 200; Tabela serial nova, só select e insert com grant: id serial → sequence com UPDATE → insert, HTTP 201.](https://madsalt.tech/charts/pt/supabase-october-30-grant-change-works.svg)

*A minha reprodução numa stack local do Supabase (CLI 2.109.1, Postgres 17.6, PostgREST 14.14), 26 de outubro de 2026.*

| Objeto, criado depois da mudança salvo indicação                 | Chave   | Resultado                                                          |
| ---------------------------------------------------------------- | ------- | ------------------------------------------------------------------ |
| Tabela criada antes da mudança                                   | pública | HTTP 200, 2 linhas                                                 |
| Tabela nova com RLS e uma política `to anon, authenticated`      | pública | HTTP 401, 42501, hint `GRANT SELECT ON public.new_orders TO anon;` |
| A mesma tabela nova                                              | secret  | HTTP 403, 42501, hint `… TO service_role;`                         |
| View nova (`security_invoker`) sobre a tabela antiga             | pública | HTTP 401, 42501, `permission denied for view`, hint `null`         |
| Cópia feita com `create table … (like old_orders including all)` | pública | HTTP 401, 42501                                                    |
| A tabela antiga depois de `drop table` e novo `create table`     | pública | HTTP 401, 42501                                                    |
| Função nova `public.ping()` em `/rest/v1/rpc`                    | pública | HTTP 200                                                           |
| Tabela nova com `id serial`, só `select, insert` dados a `anon`  | pública | insert HTTP 201                                                    |

Cinco coisas nesta tabela merecem ser sabidas antes da mudança.

**"As tabelas existentes mantêm os grants" só vale enquanto a tabela existir.** Uma migration que apaga e recria uma tabela, uma cópia com `create table … (like …)` ou um restauro a partir de um dump criam uma tabela nova, e uma tabela nova não recebe nada. A política voltou com o meu `create policy`, os grants não. Tudo o que, no seu pipeline, reconstrói tabelas em vez de as alterar falha na primeira execução depois da mudança.

**A secret key também falha.** O `service_role` ignora o RLS, mas continua a precisar de um grant. Por isso uma Edge Function, um cron job ou um handler de webhook que escreve numa tabela nova falha com 403, e não só o browser. A hint nomeia o `service_role`, e é assim que se distingue.

**As views não dão hint.** Nas tabelas, o PostgREST devolve o `GRANT` exato de que precisa. Na minha view nova a hint foi `null` e a mensagem dizia `permission denied for view v_old_orders`. Uma ferramenta de IA que corrige o 42501 a partir da hint não tem aqui nada para copiar; o grant é `grant select on public.v_old_orders to authenticated;`, e a view deve manter `security_invoker = true` para o RLS da tabela continuar a aplicar-se.

**As funções não fazem parte desta mudança.** As duas instruções tocam em tabelas e sequences. A minha função nova continuou chamável por `anon`, `authenticated` e `service_role`, porque o Postgres dá `execute` em cada função nova a `PUBLIC` e um revoke ao nível do schema não o consegue retirar, como explica o [guia de segurança da API do Supabase](https://supabase.com/docs/guides/api/securing-your-api). As chamadas RPC continuam a funcionar depois de 30 de outubro; isso significa também que uma função `security definer` nova é chamável com a sua chave pública logo que existe.

**Os inserts em `serial` continuaram a funcionar sem grant na sequence.** Muitos conselhos dizem que um id `serial` precisa de `grant usage on sequence … to authenticated` depois da mudança. Na minha execução o insert passou sem ele: o revoke retira `usage` e `select` nas sequences novas, mas o `update` fica, e o `update` chega para o `nextval`. Se revogar todos os default privileges das sequences, como faz o revoke completo do guia de segurança da API, aí precisa do grant de `usage`. Uma coluna identity (`generated by default as identity`) não precisa de nada, em qualquer dos casos.

Mais uma coisa que o revoke deixa para trás: a tabela nova continuou a vir com `TRUNCATE`, `REFERENCES` e `TRIGGER` para as três funções, e no Postgres 17 os defaults trazem também `MAINTAIN`. Nenhuma política de RLS se aplica ao `TRUNCATE`. Tratei disso na [nota sobre o 42501](https://madsalt.tech/pt/notes/supabase-42501-permission-denied/).

## Onde o encontra antes de 30 de outubro

A mudança no seu projeto alojado é no dia 30, mas há três sítios que já se comportam como depois dela.

**`supabase db reset` numa CLI recente.** A CLI v2.102.0 acrescentou `[api] auto_expose_new_tables` ao `config.toml`. Na 2.109.1, com a chave omitida, a minha stack nova já tinha os defaults novos: as tabelas só recebiam `MAINTAIN`, `REFERENCES`, `TRIGGER` e `TRUNCATE`, as sequences só `UPDATE`. Pô-la a `true` traz de volta os grants antigos e imprime `api.auto_expose_new_tables is deprecated and will be removed on 2026-10-30`. Por isso, se as suas tabelas locais ainda funcionam depois de um reset, veja primeiro essa chave. É também o sítio mais barato para testar: retire-a, faça reset, corra a sua app.

**Preview branches e projetos criados desde 30 de maio.** Reproduzem as suas migrations com os defaults novos. Toda a tabela cuja migration não tem `grant` já está inacessível aí. Na [discussão no GitHub](https://github.com/orgs/supabase/discussions/45329), quem tem um projeto por cliente relata exatamente isto: os projetos em produção estão bem, cada deploy novo a partir das mesmas migrations está partido.

**Uma migration de baseline que traz de volta o default antigo.** Um comentador da mesma discussão aponta que uma primeira migration feita com `supabase db pull` pode conter `alter default privileges … grant all on tables to anon, authenticated, service_role`. Corri essa instrução depois da mudança: a tabela seguinte que criei voltou a ficar acessível, HTTP 200. Em local isso esconde o problema. Aplicada ao projeto alojado, desfaz em silêncio a mudança e cada tabela futura volta a ficar aberta à chave pública, tenha RLS ou não. Procure `alter default privileges` nas suas migrations e leia cada ocorrência.

## Como verificar o projeto antes de 30 de outubro

Quatro verificações, da mais rápida à mais completa. As duas primeiras correm no SQL Editor do projeto alojado.

### 1. O seu projeto já mudou?

Esta é a query do guia de segurança da API do Supabase. Lista o que os objetos novos criados por `postgres` em `public` vão receber:

```sql
select
  case d.defaclobjtype
    when 'r' then 'tables'
    when 'S' then 'sequences'
    when 'f' then 'functions'
  end as object_type,
  a.grantee::regrole as role,
  string_agg(a.privilege_type, ', ' order by a.privilege_type) as privileges
from pg_default_acl d
cross join lateral aclexplode(d.defaclacl) a
where d.defaclrole = 'postgres'::regrole
  and d.defaclnamespace = 'public'::regnamespace
  and a.grantee in ('anon'::regrole, 'authenticated'::regrole, 'service_role'::regrole)
group by 1, 2
order by 1, 2;
```

Se as linhas de `tables` contêm `SELECT, INSERT, UPDATE, DELETE`, o projeto ainda está no default antigo e muda no dia 30. Se só mostram `MAINTAIN, REFERENCES, TRIGGER, TRUNCATE` (ou nenhuma linha), as tabelas novas já estão fechadas. Também o vê no Dashboard, nas definições da Data API, "Automatically expose new tables".

### 2. Que tabelas têm política mas não têm grant?

Uma política para uma função que não tem grant para esse comando nunca corre: o pedido falha antes do RLS. Esta query lista esses pares. No meu laboratório encontrou a tabela nova e a antiga recriada, e mais nada:

```sql
select p.schemaname || '.' || p.tablename as "table", r.role, p.cmd
from pg_policies p
cross join lateral unnest(case when 'public' = any(p.roles) then array['anon', 'authenticated']::name[] else p.roles end) as r(role)
join pg_class c on c.relname = p.tablename
join pg_namespace n on n.oid = c.relnamespace and n.nspname = p.schemaname
where p.schemaname = 'public'
  and r.role in ('anon', 'authenticated')
  and not has_table_privilege(r.role::text, c.oid,
        case p.cmd when 'ALL' then 'SELECT' else p.cmd end)
order by 1, 2, 3;
```

Corra-a depois de cada migration, no CI contra um `db reset` limpo ou no staging. Num projeto saudável não devolve nada; depois do dia 30, cada linha é uma tabela que a sua app não alcança. Verifica grants ao nível da tabela, por isso uma tabela aberta só com grants por coluna (`grant select (id, name) on …`) aparece aqui mesmo que essas colunas sejam legíveis.

### 3. Que migrations criam uma tabela sem grant?

A base de dados só mostra as tabelas que existem. Uma migration que ainda não aplicou só é visível no repositório. Este grep rudimentar lista cada `create table` em `supabase/migrations` sem `grant … on` para o mesmo nome:

```bash
for t in $(grep -hioE 'create table( if not exists)? +[a-z0-9_."]+' supabase/migrations/*.sql | awk '{print $NF}' | sort -u); do
  grep -qiF "on $t " supabase/migrations/*.sql || grep -qiF "on table $t " supabase/migrations/*.sql || echo "no grant: $t"
done
```

É correspondência de texto, não parsing de SQL: um grant que lista várias tabelas numa instrução, ou uma tabela nomeada sem schema num sítio e com schema noutro, dá um falso positivo. Leia cada linha que imprime. As tabelas que só o código de servidor alcança por ligação direta não precisam de grant nenhum.

### 4. Reproduza tudo com os defaults novos

Retire `auto_expose_new_tables` do `config.toml`, corra `supabase db reset` e corra a sua app ou os testes contra isso. É o seu projeto depois do dia 30, mais uma cópia nova de cada tabela, que é o caso mais difícil. O Supabase também lista as tabelas afetadas no Security Advisor e marca a exposição na Data API no Table Editor.

## Como corrigir sem abrir todas as tabelas

Escreva o grant, o RLS e as políticas na migration que cria a tabela, e dê a cada função só o que a app usa:

```sql
create table public.invoices (
  id bigint generated by default as identity primary key,
  user_id uuid not null default auth.uid(),
  total numeric not null
);

revoke all on table public.invoices from anon, authenticated, service_role;
grant select, insert on table public.invoices to authenticated;
grant select, insert, update, delete on table public.invoices to service_role;

alter table public.invoices enable row level security;

create policy "Owners read their invoices"
on public.invoices for select
to authenticated
using ((select auth.uid()) = user_id);

create policy "Owners add their invoices"
on public.invoices for insert
to authenticated
with check ((select auth.uid()) = user_id);
```

Corri este bloco na mesma stack local: um utilizador de teste com sessão iniciada inseriu uma linha e leu-a de volta (HTTP 201, depois 200), a chave pública sem sessão recebeu 42501, e a verificação 2 acima deixou de listar a tabela. O `revoke all` inicial limpa o `TRUNCATE`, o `TRIGGER` e o `REFERENCES` que vêm com uma tabela nova, e dá o mesmo resultado num projeto que ainda não mudou. Dê grant a `anon` só em dados pensados para visitantes sem sessão, e a `service_role` só se o código de servidor usar a secret key nesta tabela. Como grants e políticas se encaixam numa app inteira está na minha [checklist de segurança para apps Lovable](https://madsalt.tech/pt/blog/lovable-app-security-checklist/).

O que não fazer quando chega o primeiro 42501:

- **Não corra `grant all on all tables in schema public to anon, authenticated`.** Repõe o default antigo em todas as tabelas de uma vez, incluindo qualquer tabela onde o RLS nunca foi ativado, e essas passam a ser legíveis por quem tiver a sua chave pública. A [nota sobre o 42501](https://madsalt.tech/pt/notes/supabase-42501-permission-denied/) mostra porque é que o grant é o primeiro cadeado e o RLS o segundo.
- **Não volte a pôr `alter default privileges … grant`.** É o rollback da mudança inteira.
- **Não desative o RLS para "testar" e não ponha a secret key no frontend.** A secret key ignora o RLS para toda a gente que abrir o seu bundle; a diferença entre as chaves está em [anon key vs service role key](https://madsalt.tech/pt/blog/supabase-anon-key-vs-service-role-key/).

Se uma ferramenta de IA escreve as suas migrations, acrescente uma linha às instruções dela: "every new table in public gets explicit grants per role, enable row level security and policies in the same migration; never grant on all tables in schema public". A hint do erro ensina-lhe o grant, nunca o RLS que lhe pertence.

## Perguntas frequentes sobre a mudança

**As minhas tabelas existentes vão deixar de funcionar a 30 de outubro?** Não. Os default privileges aplicam-se a objetos criados depois, e na minha execução a tabela antiga respondeu HTTP 200 depois da mudança. A exceção é uma tabela que as suas migrations apagam e criam de novo.

**Tenho de fazer alguma coisa se nunca crio tabelas?** No dia 30, não. Verifique da próxima vez que você, um colega ou uma ferramenta de IA acrescentar uma, e verifique qualquer pipeline que crie projetos ou branches a partir das suas migrations.

**Afeta as Edge Functions?** Sim, se usarem `supabase-js` com a secret key numa tabela nova: o `service_role` também precisa de um grant. As funções que se ligam diretamente ao Postgres não são afetadas.

**Afeta funções RPC, o Storage ou o Auth?** Não nesta mudança. As funções novas continuam chamáveis através de `PUBLIC`, e `storage` e `auth` não são o schema `public`.

**Posso mudar antes do prazo?** Sim. As duas instruções do início são a mudança; corra-as primeiro no staging, depois em produção. As tabelas existentes mantêm os grants, por isso uma app em funcionamento não nota nada, e a tabela nova seguinte diz-lhe logo se as suas migrations estão prontas.

## Como o verificador vê isto

O verificador liga-se em modo só de leitura e lê os grants de cada tabela dos seus schemas expostos: em que tabelas a chave pública pode escrever sem política, que tabelas têm o RLS desligado e que default privileges ainda dão a cada tabela nova `TRUNCATE`, `REFERENCES` ou `TRIGGER`. Também lista todas as políticas cuja função não tem grant para esse comando, as tabelas que a verificação 2 encontra, exceto as que têm RLS desligado ou grants por coluna. Não vê ficheiros de migration que ainda não aplicou, por isso a verificação 3 acima continua a ser sua. Se o RLS é a parte de que tem menos certeza, [como testar o RLS do Supabase](https://madsalt.tech/pt/blog/how-to-test-supabase-rls/) explica-o passo a passo, e a [nota sobre os schemas expostos](https://madsalt.tech/pt/notes/supabase-exposed-schemas/) cobre os schemas que não são `public`, que esta mudança deixa em paz.
