---
title: "Supabase 42501 permission denied: um grant, não o RLS"
description: "Porque é que o Supabase devolve 42501 permission denied numa tabela, o que muda a 30 de outubro de 2026 e o grant, RLS e política que o corrigem, em local."
url: "https://madsalt.tech/pt/notes/supabase-42501-permission-denied/"
author: "Denys Zaichenko"
language: "pt"
published: 2026-10-26
---

# Supabase 42501 permission denied: um grant, não o RLS

Em resumo

`42501 permission denied for table …` significa que a função não tem nenhum grant sobre a tabela, por isso o Postgres pára o pedido antes de qualquer política de RLS correr. A partir de 30 de outubro de 2026, o Supabase deixa de conceder automaticamente as tabelas novas de `public` nos projetos existentes, por isso as tabelas criadas depois respondem 42501 até lhes dar o grant. A correção são três instruções juntas: um grant para as operações de que a função precisa, `enable row level security` e uma política. Conceder só o grant, sem RLS, abre a tabela a toda a gente.

Criei 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, e o 42501 é o erro que aparece quando os grants falham no sentido contrário: a menos em vez de a mais. Aqui fica o que significa e o que vi ao reproduzi-lo.

## O que significa o erro 42501 no Supabase?

42501 é o código do Postgres para privilégio insuficiente. O Postgres verifica duas camadas antes de um cliente tocar numa tabela: os grants decidem se a função pode executar a operação, e as políticas de RLS decidem que linhas. Um grant em falta falha primeiro, como um erro. Uma política que não corresponde a nada falha em silêncio, como uma lista vazia. Por isso, quando vir 42501, olhe para os grants, não para as políticas.

O PostgREST, a Data API por trás do `supabase-js`, passa o erro com uma dica. Na minha stack local (Supabase CLI 2.109.1, Postgres 17.6, PostgREST 14.14), um pedido com a publishable key a uma tabela sem grant para `anon` devolveu HTTP 401 e:

```json
{
  "code": "42501",
  "details": null,
  "hint": "Grant the required privileges to the current role with: GRANT SELECT ON public.orders TO anon;",
  "message": "permission denied for table orders"
}
```

## Porque é que as tabelas novas dão 42501 depois de 30 de outubro de 2026?

Até agora, uma tabela nova em `public` recebia `select`, `insert`, `update` e `delete` para `anon`, `authenticated` e `service_role` através de privilégios por omissão, por isso era alcançável pela Data API no momento em que era criada, estivesse protegida ou não. O [changelog](https://supabase.com/changelog/45329-breaking-change-tables-not-exposed-to-data-and-graphql-api-automatically) do Supabase remove esses valores por omissão: opcional desde 28 de abril de 2026, por omissão nos projetos novos desde 30 de maio de 2026, e aplicado a todos os projetos existentes a 30 de outubro de 2026. As tabelas existentes mantêm os grants.

O que mais a mudança parte e como verificar o projeto antes: [Supabase, 30 de outubro: o que falha com os novos grants](https://madsalt.tech/pt/blog/supabase-october-30-grant-change/).

Apliquei a mesma alteração ao meu projeto local com as duas instruções `alter default privileges … revoke` do changelog e criei uma tabela nova, `invoices`:

![O que uma tabela nova responde depois de 30 de outubro de 2026. Supabase local com os novos privilégios por omissão, um pedido cada. Publishable key, sem grant: create table → sem grant → 42501, HTTP 401; Secret key, sem grant: create table → sem grant → 42501, HTTP 403; Depois de grant select e ligar o RLS: grant select → RLS ligado → 0 linhas, HTTP 200.](https://madsalt.tech/charts/pt/supabase-42501-permission-denied.svg)

*A minha reprodução numa stack local do Supabase com os privilégios por omissão do changelog do Supabase, 23 de outubro de 2026.*

| Pedido à tabela nova                                                    | Resultado                                                        |
| ----------------------------------------------------------------------- | ---------------------------------------------------------------- |
| Publishable key, sem grant                                              | HTTP 401, 42501, dica `GRANT SELECT ON public.invoices TO anon;` |
| Secret key, sem grant                                                   | HTTP 403, 42501, dica `… TO service_role;`                       |
| Publishable key, depois de `grant select` e `enable row level security` | HTTP 200, `[]` até uma política permitir linhas                  |

Duas coisas surpreenderam-me. A secret key também é recusada: o `service_role` ignora o RLS, mas continua a precisar de um grant, por isso uma Edge Function ou uma tarefa de servidor que escreva numa tabela nova parte-se da mesma maneira. E o revoke retira apenas essas quatro operações: a tabela nova continuou a vir com `REFERENCES`, `TRIGGER` e `TRUNCATE` para `anon`, `authenticated` e `service_role`. Nenhuma política se aplica ao `TRUNCATE`.

## Como corrigir o 42501 sem abrir a tabela?

Trate o grant, o RLS e a política como uma só alteração, numa só migração, e dê a cada função apenas o que ela precisa:

```sql
revoke all on table public.invoices from anon, authenticated;
grant select, insert, update 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);
```

O `revoke all` inicial limpa o `TRUNCATE`, o `TRIGGER` e o `REFERENCES` que vieram com a tabela. Não responda ao 42501 com `grant all on all tables in schema public to anon`: isso é repor o antigo valor por omissão, para todas as tabelas de uma vez, e qualquer tabela sem RLS passa a ser legível por quem tiver a sua chave pública.

Se uma ferramenta de IA cria as suas tabelas, diga-lhe para emitir os grants, o RLS e as políticas na mesma migração. A dica do erro está escrita para que um agente consiga corrigir o grant sozinho, mas a dica nomeia só o grant, não o RLS que devia vir com ele.

## Como é que o verificador vê isto?

Quatro das suas regras leem a camada dos grants: "Privilégios por omissão que abrem novos objetos à API" assinala os projetos que ainda concedem as tabelas novas automaticamente, "Permissões de escrita sem uso para visitantes anónimos" encontra grants de escrita do `anon` que nenhuma política usa, e "Privilégios de tabela desnecessários para utilizadores com sessão iniciada" encontra `TRUNCATE`, `TRIGGER` e `REFERENCES` como os de cima. "Políticas que nunca correm: a função não tem grant" lista as tabelas em que uma política nomeia uma função sem grant para esse comando, que é o aspeto de uma tabela nova depois de 30 de outubro. Porque é que o RLS continua a importar depois do grant está em [como testar o Supabase RLS](https://madsalt.tech/pt/blog/how-to-test-supabase-rls/), e o aspeto dos grants e das políticas numa app inteira está na minha [lista de segurança para apps Lovable](https://madsalt.tech/pt/blog/lovable-app-security-checklist/).
