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.

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 é
PGRST204e 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.
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 Editor | A coluna que foi criada | O que seu app tem que enviar |
|---|---|---|
alter table profiles add column phone_number text; | phone_number | phone_number |
alter table profiles add column phoneNumber text; | phonenumber | phonenumber |
alter table profiles add column "phoneNumber" text; | phoneNumber | phoneNumber |
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 oorder_itemsou oprofilesanexado 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, comoconfirmPassword
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ê.
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.
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';, depoisselect 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().