Pular para o conteúdo

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.

Vlad Tkachenko12 min de leitura
Um pátio murado de tabelas de banco de dados, cada porta aberta e iluminada, atrás de um portão de entrada fechado com as grades baixadas.

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.

O Postgres verifica o schema, depois a tabela, depois as linhas, e para na primeira recusa. Um portão fechado barra a requisição antes que a porta de qualquer tabela seja sequer tentada.

É 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.
Antes do reset, as quatro funções podiam entrar no schema. Depois, as tabelas podem ser reconstruídas, mas o portão e as portas delas só abrem para postgres, que as construiu.

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 table significa 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:

  1. Um grant por tabela, para as funções que precisam dele. Uma tabela de pedidos não precisa de grant nenhum para anon.
  2. Row Level Security em toda tabela, com uma regra.Como ligar e conferir se ele segura.
  3. Tabelas novas fechadas por padrão. Revogar os grants automáticos faz isso hoje, e o 30 de outubro faz isso por você.
  4. 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 no usage.
  • 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ê create ao 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.

Escrito por

Vlad Tkachenko

Fundador da Reeve

Passo meus dias olhando apps criados com Lovable, Bolt, v0, Cursor e Replit, e a curta lista de erros que aparecem neles sem parar.

Mais sobre o autor

Leia depois

Todos os artigos

Faça backup do seu banco Supabase antes da próxima mudança

O Reeve guarda uma cópia criptografada em horários programados e a restaura para você se algo quebrar.

Ver backups do Supabase

Verificação externa automatizada, não uma auditoria completa. A ausência de achados não é garantia de segurança.