Pular para o conteúdo

Noções de segurança

Supabase PGRST204: "Could not find the column in the schema cache"

Supabase "Could not find the column in the schema cache" costuma ser um nome que não bate. Quatro verificações e a linha que recarrega.

Vlad Tkachenko12 min de leitura
Um painel de saguão com um espaço vazio na lista de salas e, na frente, uma guia de entrega com uma linha acesa em âmbar.

Em resumo

  • Supabase "Could not find the column in the schema cache" (PGRST204) significa que seu app tentou salvar um campo que a tabela não lista. O PostgREST compara cada chave de um insert ou update com a própria cópia do seu schema, letra por letra.
  • A causa de costume é o nome: phoneNumber enviado para uma coluna chamada phone_number, ou uma chave que nunca foi coluna, como linhas relacionadas aninhadas dentro de um insert.
  • O Supabase recarrega essa cópia sozinho sempre que uma tabela muda. Quando ela está mesmo desatualizada, NOTIFY pgrst, 'reload schema'; no SQL Editor recarrega a cópia enquanto o app continua no ar.

Você pediu ao seu builder para colocar um telefone no formulário de perfil. A coluna está lá no Table Editor. Você clica em Salvar, nada acontece, e o console do navegador mostra isto:

{
  "code": "PGRST204",
  "details": null,
  "hint": null,
  "message": "Could not find the 'phoneNumber' column of 'profiles' in the schema cache"
}

Supabase "Could not find the column in the schema cache" significa que seu app tentou salvar um campo que a tabela, do jeito que o PostgREST a enxerga, não tem. O PostgREST é a parte do Supabase que transforma as chamadas supabase.from(...) do seu app em consultas ao banco de dados. Ele mantém a própria lista de cada tabela e cada coluna, chamada schema cache, para não precisar perguntar ao banco sobre a estrutura dele a cada requisição.

Aqui está a parte que quase toda resposta erra: ela manda você recarregar o schema cache. No Supabase o cache se recarrega sozinho toda vez que uma tabela muda. Então, se você acabou de adicionar uma coluna e ainda vê esse erro, o cache é o motivo menos provável. Geralmente é o nome.

O que significa "Could not find the column in the schema cache"?

Uma chave de uma escrita que seu app enviou não tem coluna correspondente naquela tabela.

Pense no schema cache como o painel do saguão de um prédio: uma linha para cada escritório, que é uma tabela, e embaixo cada sala, que é uma coluna. Cada requisição do seu app é uma guia de entrega. O PostgREST confere cada linha da guia com o painel, e uma sala que o painel não lista é recusada na recepção, antes que qualquer coisa chegue aos seus dados.

A mensagem nomeia os dois lados dessa comparação. Em Could not find the 'phoneNumber' column of 'profiles' in the schema cache, phoneNumber é o que seu app enviou e profiles a tabela para onde foi. Outras três coisas estão fixadas no próprio código do PostgREST:

  • o código é PGRST204 e o status é HTTP 400, o que quer dizer que a própria requisição estava errada
  • ele só vem de escritas: .insert(), .update() e .upsert(). Uma leitura que cita uma coluna inexistente falha com outro erro, escrito pelo Postgres
  • ele não traz nenhuma dica. Para uma tabela que falta, o PostgREST sugere o nome mais parecido que conhece; para uma coluna que falta, hint é null, e a comparação fica com você

Cada chave do objeto que você passa para uma dessas três chamadas tem que ser uma coluna daquela tabela, escrita do jeito que a tabela escreve.

O PostgREST compara cada chave que seu app envia com as colunas da lista dele, letra por letra. phoneNumber e phone_number são dois nomes diferentes.

Causa 1: o nome não bate, letra por letra

A conferência é exata. Para o PostgREST, phoneNumber, phone_number e phonenumber são três colunas diferentes, e sua tabela tem no máximo uma delas.

Há dois jeitos comuns de chegar aqui. O primeiro são dois hábitos de nomes que se encontram: o JavaScript costuma escrever um campo como phoneNumber, e uma coluna do Postgres costuma se chamar phone_number. O código que seu builder escreveu e o SQL que ele rodou podem estar certos cada um do seu lado e mesmo assim não bater.

O segundo é a armadilha das aspas. Em SQL, um nome digitado sem aspas duplas é convertido para minúsculas. A documentação do Postgres diz com todas as letras: "Quoting an identifier also makes it case-sensitive, whereas unquoted names are always folded to lower case."

O que você rodou no SQL EditorA coluna que foi criadaO que seu app tem que enviar
alter table profiles add column phone_number text;phone_numberphone_number
alter table profiles add column phoneNumber text;phonenumberphonenumber
alter table profiles add column "phoneNumber" text;phoneNumberphoneNumber

A linha do meio é a que engana. O SQL parece ter criado phoneNumber, o código envia phoneNumber, e a coluna é phonenumber.

Para ver os nomes de verdade, abra a tabela no Table Editor e leia os cabeçalhos das colunas, ou rode isto no SQL Editor:

select column_name
from information_schema.columns
where table_schema = 'public' and table_name = 'profiles';

Depois veja o que seu app enviou. Nas ferramentas de desenvolvedor do navegador, abra a aba Rede (Network), clique na requisição que falhou para /rest/v1/profiles e leia o payload dela.

A correção mais segura é mudar o app, porque isso não mexe em nada dos seus dados. Peça ao seu builder para usar o nome exato da coluna em todo lugar onde esse campo é salvo: "a coluna em profiles é phone_number; use esse nome em todo insert e update". Renomear a coluna em vez disso é uma mudança numa tabela em uso, e qualquer outra tela que ainda use o nome antigo quebra na primeira vez que rodar.

Se você constrói com Lovable, volte no chat procurando uma mudança no banco que você precisava aprovar. A documentação de Supabase do Lovable diz que ele "writes the SQL, shows it to you, and asks for your approval in the project chat before running it". Até você aprovar, a coluna não existe, não importa o que o código espere.

Causa 2: uma chave que nunca foi coluna

A requisição leva algo que a tabela nunca deveria guardar: linhas relacionadas aninhadas dentro do insert, ou um campo que o formulário guarda para si.

O insert aninhado é o que as pessoas esperam que funcione:

await supabase.from('orders').insert({
  customer_id: user.id,
  total: 42,
  order_items: [{ product_id: 7, quantity: 2 }],
})

Isso falha com Could not find the 'order_items' column of 'orders' in the schema cache. Ler o mesmo formato funciona: .select('*, order_items(*)') devolve um pedido com os itens dele, então parece que o caminho inverso também deveria funcionar. O PostgREST lê cada chave de uma escrita como coluna da única tabela para onde a requisição vai. Um usuário do Supabase relatou isso como bug, e a resposta, antes de a issue ser fechada em 18 de outubro de 2026, foi que inserts aninhados são um recurso que falta. O pedido por eles está aberto no tracker do PostgREST desde fevereiro de 2017.

Outras duas formas do mesmo problema:

  • uma linha lida junto com as linhas relacionadas, editada num formulário e devolvida inteira em .update(), de modo que o order_items ou o profiles anexado vai junto
  • o estado inteiro de um formulário passado direto para .insert(), com campos que o formulário precisa e a tabela não, como confirmPassword

A correção é gravar cada tabela separadamente, o pai primeiro. Insira o pedido e peça o id dele de volta com .select('id').single(), depois insira os itens com esse order_id. São duas requisições, então se a segunda falhar o pedido fica salvo sem os itens. Quando isso não pode acontecer nunca, coloque os dois inserts numa função do Postgres e chame com .rpc(), que a resposta naquela issue chama de padrão. Aí tudo é salvo, ou nada. Se essa chamada responder PGRST202, a versão deste erro para funções explica por quê.

Uma guia aninhada é lida como mais uma coluna da primeira tabela. Enviadas separadamente, o pai primeiro, cada uma encontra o próprio painel.

Causa 3: a mudança foi para outro projeto

A coluna existe, num banco com que seu app nunca conversa. Uma conta do Supabase pode ter vários projetos, e uma branch é um projeto separado com o próprio banco. Uma coluna adicionada no SQL Editor do projeto errado muda o painel de outro prédio.

Na issue #42183 do Supabase, um relato de que o cache "does not update" não importa o que se tentasse, um dos desenvolvedores do PostgREST respondeu que o relato citava duas referências de projeto diferentes, e pediu para confirmar que a mudança tinha sido feita no projeto para onde as requisições iam.

A conferência usa dois endereços. Seu app chama https://<reference>.supabase.co, que você vê em qualquer requisição na aba Rede. O painel mostra o projeto em que você está em supabase.com/dashboard/project/<reference>. As duas referências têm que ser a mesma sequência de caracteres. Qual projeto é o meu explica como achar quando outra pessoa configurou o Supabase.

Causa 4: o cache está mesmo desatualizado

É o caso raro no Supabase, porque o Supabase recarrega o cache por você. Todo banco do Supabase é configurado com um gatilho de evento, pgrst_ddl_watch, que roda depois de cada mudança na estrutura do banco, ALTER TABLE incluído, e envia NOTIFY pgrst, 'reload schema': a mensagem que manda o PostgREST reconstruir a lista. Dá para ler isso na configuração de banco do Supabase. Adicionar uma coluna no Table Editor ou no SQL Editor repinta o painel sozinho.

Dá errado quando essa mensagem nunca chega. O guia de solução de problemas do Supabase descreve o caso: "an underlying Postgres notification queue issue prevents PostgREST from receiving cache reload signals after schema changes". O log do PostgREST mostra então Failed listening for database notifications on the "pgrst" channel, e o painel fica como estava, não importa quantas colunas você adicione.

Cada ALTER TABLE no Supabase envia a mensagem de recarga sozinho. Um cache desatualizado quer dizer que essa mensagem não chegou.

Como recarrego o schema cache do Supabase?

Rode uma linha no SQL Editor:

NOTIFY pgrst, 'reload schema';

A página do Supabase sobre isso diz que você deve ver "Success. No rows returned" em Results. É seguro num app em uso. A documentação do PostgREST diz "Requests will wait until the schema cache reload is done", então ninguém usando o app vê erro enquanto isso.

Se o problema é a fila de notificações, a mensagem de recarga fica presa do mesmo jeito que a automática. O guia de solução de problemas dá uma segunda linha para esse caso:

select pg_notification_queue_usage();

Segundo o Supabase, ela faz o PostgREST atualizar o cache e "does not require a database instance or PostgREST service restart". Depois tente salvar de novo.

Quando recarregar não resolve

Reinicie o projeto e, se o erro sobreviver a isso, volte às causas 1 a 3.

Um reinício sempre dá ao PostgREST uma lista nova. O mesmo desenvolvedor do PostgREST escreveu na #42183 que um PostgREST reiniciado "won't go live if it doesn't load a new schema cache". Restart project fica em Project Settings, depois General, e como reiniciar um projeto do Supabase mostra o passo a passo, incluindo os poucos minutos em que ele fica fora do ar.

Então um erro que sobrevive a um reinício vem de uma lista nova e exata: aquela tabela, naquele projeto, não tem uma coluna com esse nome. A própria #42183, aberta em janeiro de 2026 por alguém que já tinha tentado recarregar, reiniciar e apagar e recriar a coluna, ainda estava aberta quando a lemos em 21 de outubro de 2026. A resposta do desenvolvedor ali também sugeria atualizar para o PostgREST 14 nas configurações de Infrastructure do projeto.

Risque apagar e recriar da sua lista. alter table … drop column apaga cada valor guardado naquela coluna, e a coluna nova com o mesmo nome começa vazia. Se você está prestes a mudar uma tabela em que usuários de verdade gravam dados, guarde uma cópia do banco de antes da mudança. O Reeve Care faz uma num cronograma e a guarda criptografada, no armazenamento próprio da Reeve na UE.

Mantendo seus tipos gerados em dia

Gere de novo seus tipos de TypeScript depois de cada mudança numa tabela, e um nome de coluna escrito errado pode aparecer no editor antes de chegar ao site no ar.

O Supabase monta o arquivo a partir das suas colunas reais, pelo painel ou com um comando do guia de geração de tipos dele:

npx supabase gen types typescript --project-id "$PROJECT_REF" --schema public > database.types.ts

Quando o cliente é criado com esses tipos, createClient<Database>(…), o tipo Insert de cada tabela lista as colunas que o .insert() aceita. Uma coluna escrita errado num objeto escrito por inteiro dentro da chamada é então marcada como erro antes de você publicar. A documentação do Lovable diz que ele "regenerates the TypeScript types your app uses" depois de uma mudança no banco. Um arquivo de tipos gerado antes da sua última mudança na tabela ainda lista as colunas antigas.

Mudar uma tabela em uso sem perder o que está nela

Guarde uma cópia de antes da mudança e saiba na hora se o app continua respondendo depois.

O Reeve Care guarda uma cópia do seu banco do Supabase:

  • uma cópia criptografada num cronograma, no armazenamento próprio da Reeve na UE, fora da sua conta do Supabase
  • a última cópia antes da sua mudança tem as tabelas e colunas como eram, com as linhas
  • cada cópia verificada antes de contar, contando as linhas de cada tabela
  • também seus arquivos enviados, assim que você conecta uma credencial do Storage
  • restauração com um clique, com uma cópia de segurança do estado atual feita antes
  • tudo o que o Monitor faz

O Reeve Monitor continua verificando de fora:

  • se o seu app está no ar, verificado a cada minuto, para separar fácil um botão Salvar quebrado de uma queda
  • a verificação de segurança completa a cada hora, em até três apps
  • uma mensagem quando um resultado muda, para que um deploy que muda o que seu app expõe chegue até você sem você ir olhar
  • um relatório mensal, em linguagem simples, do que ele viu

O que fazer agora

O que fazer

  • Leia os dois nomes na mensagem: a chave que seu app enviou e a tabela para onde ela foi.
  • Compare essa chave com a coluna no Table Editor, letra por letra. Um nome digitado em SQL sem aspas fica salvo em minúsculas.
  • Procure chaves que não são colunas: linhas relacionadas aninhadas num insert, ou uma linha inteira devolvida com as linhas relacionadas. Grave cada tabela separadamente, ou coloque as duas escritas numa função.
  • Confira se a referência no endereço do Supabase do seu app bate com o projeto que você mudou.
  • Se tudo isso bater, rode NOTIFY pgrst, 'reload schema';, depois select pg_notification_queue_usage();, e só reinicie o projeto se nenhum dos dois ajudar.
  • Gere de novo seus tipos de TypeScript depois de cada mudança numa tabela.

Comece pelo nome de coluna que a mensagem cita, porque é exatamente o que seu app enviou, e o Table Editor mostra exatamente o que a tabela tem. Antes de renomear ou apagar qualquer coisa numa tabela que as pessoas usam, guarde uma cópia do banco de antes da mudança.

Perguntas frequentes

O que significa PGRST204?

PGRST204 é o código que o PostgREST, a parte do Supabase que responde às chamadas do seu app ao banco de dados, dá a uma escrita que cita uma coluna que a tabela não tem. Ele volta com HTTP 400 e a mensagem "Could not find the 'x' column of 'y' in the schema cache", em que x é a chave que seu app enviou e y a tabela para onde foi. Inserts, updates e upserts o produzem.

Eu adicionei a coluna e continua falhando. Por quê?

Geralmente é o nome. Confira se seu app envia exatamente o nome salvo (uma coluna adicionada em SQL como phoneNumber, sem aspas, fica salva como phonenumber), se você a adicionou no projeto que o seu app chama, e se nada mais na requisição é uma chave que a tabela não tem. O Supabase recarrega o schema cache sozinho depois de cada ALTER TABLE, então um cache desatualizado é a última coisa a suspeitar.

Como recarrego o schema cache do Supabase?

Rode NOTIFY pgrst, 'reload schema'; no SQL Editor. Se nada mudar, o guia de solução de problemas do Supabase dá um segundo comando, select pg_notification_queue_usage();, para quando as mensagens de recarga ficam presas. As requisições que chegam durante a recarga esperam ela terminar.

Pausar o projeto limpa o cache?

Sim. O PostgREST monta um schema cache novo toda vez que inicia, e retomar um projeto pausado o inicia de novo. Restart project, em Project Settings e depois General, faz o mesmo sem a pausa. Se o erro sobreviver a qualquer um dos dois, o cache estava certo desde o começo: a coluna falta naquela tabela, naquele projeto.

Por que inserts aninhados falham com esse erro?

Porque o PostgREST lê cada chave de um insert como coluna de uma única tabela, e uma chave com linhas relacionadas é uma coluna que ele não encontra. Ler linhas relacionadas numa requisição funciona; gravá-las assim nunca foi suportado. Insira primeiro o pai e depois os filhos com o id dele, ou faça as duas coisas numa função do Postgres chamada com rpc().

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.