Noções de segurança
Supabase "permission denied for schema public": três causas
Supabase "permission denied for schema public" é um grant que falta no schema. O que o removeu, e a linha que o devolve.

Em resumo
- Supabase "permission denied for schema public" significa que a função que faz a requisição não tem nenhum direito de entrar no schema public, então o Postgres recusou antes de olhar uma única tabela.
- Todo projeto do Supabase começa com esse direito concedido a anon, authenticated e service_role. Se o seu app recebe esse erro, algo o removeu: o schema foi apagado e criado de novo, ou um script revogou o grant.
- Devolvê-lo é uma linha de SQL. O grant só deixa uma função chegar às suas tabelas, e o Row Level Security continua decidindo quais linhas cada visitante recebe, então confira essas regras antes de dar o problema por resolvido.
Ontem o seu app funcionava. Hoje cada tela que lê do Supabase aparece vazia ou
mostra um erro, e o console do navegador tem a mesma linha para todas:
permission denied for schema public. Você abre o painel do Supabase e todas as
tabelas continuam lá. O SQL Editor lê todas sem reclamar.
A resposta por trás dessa linha costuma chegar como 401 ou 403. Cortada nos dois campos que importam, ela diz isto:
{
"code": "42501",
"message": "permission denied for schema public"
}
Supabase "permission denied for schema public" vem do Postgres, o banco de dados
em que o Supabase roda, e a lista de causas é curta. Todo projeto do Supabase é
criado com um grant que deixa o seu app entrar no schema public. Alguém, ou
algo, tirou esse grant, e devolvê-lo é uma linha de SQL.
Esta é a parte fácil de entender ao contrário: esse grant é a porta da frente do prédio, e as fechaduras dos seus dados ficam mais para dentro. Se alguém o tirou para deixar estranhos do lado de fora, deixou o seu app de fora junto. E reabrir essa porta só é seguro se as fechaduras de dentro estiverem no lugar, então são elas que você precisa conferir antes de dar o problema por resolvido.
O que significa "permission denied for schema public"?
Significa que a função que faz a requisição não tem o direito de entrar no
schema public, então o Postgres recusou antes de olhar uma única tabela.
Um schema é uma pasta de tabelas com nome dentro do seu banco de dados.
public é onde Lovable, Bolt, v0, Cursor, Claude Code e Replit colocam as suas
tabelas se ninguém disser outra coisa. Uma função (role, em inglês) é o
tipo de visitante de onde vem uma requisição: anon é alguém que não fez login,
authenticated é alguém que fez, e service_role é o seu próprio código de
servidor usando a chave secreta. Um grant dá a uma função uma permissão sobre
uma coisa.
A permissão de que esse erro fala se chama USAGE, e a
documentação do Postgres
a descreve como o direito de procurar ("look up") objetos dentro do schema. Uma
função sem ela não alcança nenhuma tabela desse schema, não importa o que digam
as configurações da própria tabela.
Se a versão de tabela deste erro
ensinou você a pensar em cada tabela como uma sala com uma porta para cada
função, o schema é o prédio onde ficam essas salas, e USAGE é a porta da
frente dele. permission denied for schema public significa que a sua
requisição está parada do lado de fora. Não importa quais salas estão com a
porta aberta, porque a requisição nunca chega a uma sala.
O Supabase dá essa porta da frente às três funções quando um projeto é criado. A imagem de banco de dados do próprio Supabase executa esta linha no primeiro script de configuração:
grant usage on schema public to postgres, anon, authenticated, service_role;
Então, se o seu app recebe esse erro, o grant existiu e não existe mais.
Por que o SQL Editor continua funcionando?
Porque o SQL Editor não usa nenhuma das funções do seu app. Um
guia de solução de problemas do Supabase
diz que uma consulta feita ali é "routed through the postgres role at the
database level", ou seja, passa pela função postgres no nível do banco de
dados.
postgres é a função dona das suas tabelas, e se o próprio schema foi recriado
pelo painel ou pelo seu builder, ela é dona dele também. Quem é dono não precisa
de grant para entrar no próprio prédio. Por isso o painel mostra todas as
tabelas, um select no SQL Editor devolve todas as linhas, e a mesma
requisição vinda do seu app é barrada na porta da frente. Nada nessas telas
avisa que as funções do seu app perderam o caminho para entrar.
Para ver o que o seu app vê, pergunte ao Postgres diretamente sobre as funções dele. Cole isto no SQL Editor:
select rolname,
has_schema_privilege(rolname, 'public', 'USAGE') as can_enter
from pg_roles
where rolname in ('anon', 'authenticated', 'service_role');
Cada linha deveria dizer true. Uma linha que diz false é uma função que o
seu app usa e que não consegue mais entrar.
O grant abre o prédio, e as fechaduras ficam lá dentro
USAGE deixa uma função chegar às tabelas de um schema. Ele não diz nada sobre
quais dessas tabelas a função pode usar, nem quais linhas ela recebe.
Atrás da porta da frente há mais duas verificações, e as duas pertencem a cada
tabela. O grant próprio da tabela decide se uma função pode usar aquela tabela,
e quando ele falta é isso que permission denied for table informa. Depois, o
Row Level Security, as regras por linha que você talvez já conheça, decide
quais linhas essa função recebe. O Postgres faz as três perguntas nessa ordem e
para na primeira recusa.
É essa ordem que faz do grant do schema uma fechadura ruim. Para uma função, ele
é tudo ou nada: tire-o de anon e cada tela que um visitante sem login vê para
de funcionar de uma vez, inclusive uma página de login que leia qualquer coisa
das suas tabelas. Tire-o de authenticated e o mesmo acontece com todo mundo
que fez login. A documentação do Postgres acrescenta que revogá-lo "is not a
completely secure way to prevent object access", ou seja, não é um jeito
totalmente seguro de impedir o acesso, e que uma função sem ele ainda consegue
ler os nomes das suas tabelas nos catálogos do sistema.
Se você está vendo esse erro, o seu app precisa da função que foi recusada.
Causa 1: o schema foi apagado e criado de novo
Essa é a causa que leva mais coisa junto. Alguém executou estas duas linhas, ou algo que faz o mesmo:
drop schema public cascade;
create schema public;
A primeira linha remove o schema e, por causa do cascade, toda tabela, view e
função que havia dentro dele. A segunda cria um public novo e vazio. Um schema
novo pertence à função que o criou, e a documentação do Postgres é clara: por
padrão, ninguém mais recebe privilégio algum sobre um schema. O grant que o
Supabase executou quando criou o seu projeto pertencia ao schema antigo e foi
embora com ele, e o Supabase não executa o script de configuração uma segunda
vez.
Essas duas linhas são o jeito mais curto de esvaziar um banco Postgres, e por isso são um passo que um assistente de IA pode dar quando você pede para começar o banco do zero ou para fazer uma migração que está falhando passar limpa.
Depois disso, as suas tabelas voltam se as migrações do app rodarem de novo. Uma migração é um dos arquivos de mudanças no banco que o seu builder escreve, e rodar todas reconstrói as tabelas que elas descrevem. O que não volta é tudo o que elas nunca mencionam, e o grant da porta da frente foi feito pelo Supabase antes de existir a sua primeira migração. As migrações que o seu builder escreveu podem nem conter esse grant.
Mais duas coisas vão embora com o schema antigo:
- Os grants automáticos para tabelas novas. O Postgres guarda esses grants como privilégios padrão (default privileges) do schema, e eles são apagados junto com ele. As tabelas criadas depois começam sem grant para nenhuma das funções do seu app.
- As linhas. Migrações reconstroem tabelas vazias. O que havia nas antigas só volta se alguém restaurou uma cópia feita antes do reset.
Se as suas tabelas voltaram vazias, encontrar essa cópia vem antes de qualquer outra coisa nesta página.
Causa 2: um revoke feito para proteger
O segundo jeito é de propósito. Alguém leu que toda tabela em public pode ser
acessada pela Data API do Supabase, o endereço web pelo qual o seu app lê e
grava os dados, e revogou o grant do schema para fechar isso.
A preocupação por trás disso é razoável, e a ferramenta escolhida é a errada. O
guia do próprio Supabase para
proteger a Data API
nunca mexe no grant do schema. O que ele revoga é o acesso automático que as
tabelas novas de public recebem, de modo que cada tabela nova continua fechada
até alguém dar acesso a ela de propósito. O Supabase liga isso para todos os
projetos existentes em 30 de outubro de 2026.
Se um script que você rodou, ou um assistente a quem você pediu para reforçar o banco, tirou o grant do schema, a consulta de verificação acima mostra quais funções o perderam. Devolva o grant às que o seu app usa e depois faça a proteção do jeito que as seções abaixo descrevem.
Causa 3: o erro vem de uma migração
Quando a mensagem aparece enquanto algo está criando tabelas, como uma
migração, um deploy, ou o Prisma ou o Drizzle preparando o seu banco, a
permissão que falta é outra. Criar qualquer coisa num schema exige CREATE
nele, e o texto do erro quando falta CREATE é a mesma linha.
Desde o Postgres 15, funções comuns não recebem mais CREATE em public por
padrão. A
documentação do Postgres
diz que só bancos atualizados a partir da versão 14 ou anterior ainda dão isso a
todo mundo. Por isso um usuário de banco separado, criado para uma ferramenta de
migração, é recusado já na primeiríssima tabela. O
guia do Prisma do Supabase
cria um usuário prisma, e a configuração dele inclui
grant create on schema public to prisma.
A solução é um grant de create só para esse usuário de migração. Ninguém que
visita o seu app precisa criar tabelas, então anon e authenticated nunca
recebem um.
Como devolver o grant com segurança
Execute uma linha, confirme que as três funções conseguem entrar, depois recarregue o seu app e leia o que ele diz em seguida.
A linha, no SQL Editor:
grant usage on schema public to anon, authenticated, service_role;
Fique no usage. grant all on schema public também acaba com o erro, e num
schema all quer dizer usage mais create, o direito de adicionar tabelas
próprias. O Supabase recomenda dar a cada função
o mínimo de que ela precisa,
e as funções dos seus visitantes só precisam entrar.
A consulta de verificação de antes agora deveria dizer true para as três
funções. Recarregue o seu app, e uma de duas coisas acontece:
- Ele funciona. A porta da frente era a única coisa que faltava.
- Uma mensagem nova cita uma tabela.
permission denied for tablesignifica que o grant próprio dessa tabela também está faltando, o que é de esperar depois da primeira causa, porque tabelas reconstruídas nunca receberam os grants automáticos. Dê a cada tabela o grant dela, o Row Level Security e uma regra numa única mudança.
Resista à tentação de dar tudo em todas as tabelas para todas as funções só
para a segunda mensagem sumir. Uma tabela com grant para anon e Row Level
Security desligado entrega todas as linhas a qualquer um que pedir, e nada no
seu app vai parecer diferente.
Se a mensagem cita outro schema, como permission denied for schema api, esse
schema foi criado por alguém e nunca recebeu os grants com que public começa.
O guia do Supabase para
usar schemas personalizados
lista as linhas de que ele precisa.
O que proteger no lugar
Os controles que mantêm estranhos longe dos seus dados pertencem a cada tabela, e o guia de segurança do Supabase cita todos:
- Um grant por tabela, para as funções que precisam dele. Uma tabela de
pedidos não precisa de grant nenhum para
anon. - Row Level Security em toda tabela, com uma regra.Como ligar e conferir se ele segura.
- Tabelas novas fechadas por padrão. Revogar os grants automáticos faz isso hoje, e o 30 de outubro faz isso por você.
- Tabelas internas fora da Data API. O guia do Supabase descreve como colocar o que o seu app lê num schema dedicado, para que todo o resto fique em schemas que a Data API não serve.
Depois olhe o resultado de fora, porque nenhuma dessas telas mostra o que um estranho recebe. O nosso scan gratuito pergunta a cada tabela que o seu app cita quantas linhas um visitante sem login receberia, lê esse número sem baixar uma única linha e leva uns 20 segundos, sem conta: escaneie seu app.
Mantendo as suas tabelas, e o que há nelas
Depois de hoje, ainda podem dar errado duas coisas: uma mudança posterior abre uma tabela para estranhos, e um reset como o da primeira causa leva as suas linhas junto.
Reeve Monitor continua verificando de fora:
- a verificação de segurança completa a cada hora, em até três apps
- uma mensagem quando um resultado muda, para que uma tabela que abriu depois de uma migração não fique esperando você olhar
- se seu app está no ar
- um relatório mensal, em linguagem simples, do que ele viu
Reeve Care guarda uma cópia do que há nas suas tabelas:
- uma cópia criptografada do seu banco de dados do Supabase num cronograma, no armazenamento próprio da Reeve na UE, fora da sua conta do Supabase
- cada cópia verificada antes de valer, contando as linhas de cada tabela
- também seus arquivos enviados, assim que você conecta uma credencial do Storage
- uma restauração com um clique, com uma cópia de segurança do estado atual feita antes
- tudo o que o Monitor faz
O que fazer agora
O que fazer
- Rode a consulta de verificação no SQL Editor. Cada função que disser
falseé uma que o seu app perdeu. - Devolva o grant com
grant usage on schema public to anon, authenticated, service_role;e fique nousage. - Se o schema foi apagado, veja se as suas tabelas estão vazias e procure uma cópia feita antes do reset.
- Se aparecer um erro de tabela em seguida, dê a essa tabela o grant, o Row Level Security e uma regra juntos.
- Proteja tabela por tabela, onde dá para ajustar, e deixe o grant do schema em paz.
- Se o erro veio de uma migração, dê
createao usuário de migração e a mais ninguém.
Comece pela consulta de verificação, porque um único resultado diz quais funções do seu app perderam o caminho para entrar. Depois, com a porta aberta de novo, escaneie seu app para ver o que ele entrega a um estranho.
Perguntas frequentes
O que significa "permission denied for schema public" no Supabase?
Significa que a função que faz a requisição, normalmente anon ou authenticated, não tem o direito de entrar no schema public, então o Postgres recusou antes de olhar qualquer tabela. O erro traz o código 42501 e costuma chegar ao seu app como 401 ou 403. Todo projeto do Supabase começa com esse direito concedido, então o erro significa que alguém ou algo o removeu.
É seguro conceder usage no schema public?
Sim. Todo projeto do Supabase começa exatamente com esse grant. Ele só deixa uma função chegar às tabelas do schema: cada tabela continua precisando do próprio grant, e o Row Level Security continua decidindo quais linhas cada visitante recebe. O que merece uma olhada são as tabelas por trás, porque uma com grant para anon e Row Level Security desligado entrega todas as linhas a qualquer um que pedir.
Qual a diferença para "permission denied for table"?
O erro de schema para a requisição antes que ela chegue a qualquer tabela, então todas as tabelas do schema falham de uma vez. O erro de tabela é sobre uma única tabela que não tem grant para aquela função, e o resto do seu app continua funcionando. Os dois trazem o código 42501, então o que os diferencia são as palavras da mensagem.
Por que minhas tabelas funcionam no SQL Editor do Supabase e não no meu app?
Porque o SQL Editor executa as suas consultas com a função postgres, que é dona das suas tabelas e não precisa do grant que as funções do seu app perderam. O seu app usa anon para visitantes sem login e authenticated para quem entrou. Pergunte ao Postgres diretamente sobre essas duas funções, com has_schema_privilege, para ver o que o seu app vê.
O que eu deveria proteger no lugar do grant do schema?
As tabelas. Dê a cada tabela um grant só para as funções que precisam dele, ligue o Row Level Security com uma regra em toda tabela e deixe as tabelas novas começarem sem nenhum acesso. O Supabase torna essa última parte o padrão para projetos existentes em 30 de outubro de 2026.