Noções de segurança
Supabase "invalid input syntax for type uuid": leia as aspas
Supabase "invalid input syntax for type uuid": seu app enviou algo que não é um id. Leia as aspas, segure a consulta, mantenha a coluna.

Em resumo
- Supabase "invalid input syntax for type uuid" (código 22P02 do Postgres, HTTP 400 no seu app) quer dizer que uma coluna uuid recebeu um valor que não é um uuid. O texto entre aspas é exatamente o que seu app enviou.
- "undefined" entre as aspas quer dizer que a consulta rodou antes de o id existir, porque o supabase-js escreve um valor que falta como essa palavra dentro da requisição. Segure a consulta até o id chegar.
- Mantenha a coluna como uuid. Trocar para text transforma um erro barulhento numa página que, em silêncio, não mostra nada.
Você abre o painel do seu app e a lista de projetos fica vazia por um instante, depois se preenche. Ou fica vazia. De qualquer jeito, o console do navegador tem uma linha vermelha, e a requisição por trás respondeu com isto:
{
"code": "22P02",
"details": null,
"hint": null,
"message": "invalid input syntax for type uuid: \"undefined\""
}
Supabase "invalid input syntax for type uuid" quer dizer que o Postgres recebeu
um valor que não é um uuid, para uma coluna que só guarda uuids, e recusou a
requisição inteira. A parte entre aspas é o que seu app enviou. Aqui é a palavra
undefined: a consulta rodou antes de existir o id de que ela precisava.
As correções rápidas que circulam para este erro são trocar a coluna para text
ou converter o valor. As duas fazem a mensagem sumir. Nenhuma dá à consulta o seu
id, então a página continua pedindo a coisa errada e para de avisar você.
O que significa "invalid input syntax for type uuid"?
Significa que um valor a caminho de uma coluna uuid não estava escrito do jeito
que se escreve um uuid, então o Postgres parou antes de executar qualquer coisa.
Nenhuma linha foi lida e nenhuma foi alterada.
Um uuid é um id num formato fixo: 32 caracteres de 0 a 9 e de a a f,
escritos em grupos de 8, 4, 4, 4 e 12, como
3f2b8c1e-9a4d-4e7b-b1c2-5d6e7f8a9b0c. O Supabase dá um id assim a cada usuário
logado, e uma coluna que registra de quem é cada linha, como user_id, guarda o
mesmo tipo de valor. O
leitor do Postgres para esse tipo
aceita "a series of 32 hexadecimal digits with an optional dash after each group
of 4 hexadecimal digits, and optionally surrounded by {}". Qualquer outra coisa
termina nesta mensagem, com a entrada citada.
Pense na coluna como uma máquina de triagem que lê um único tipo de etiqueta: um código nesses cinco grupos. Seu app colocou na esteira um pacote com uma palavra escrita onde vai o código. A máquina parou a esteira e leu a palavra para você.
| Campo | Exemplo | O que ele diz |
|---|---|---|
code | 22P02 | O código do Postgres para este erro, chamado invalid_text_representation na lista de códigos dele |
message | invalid input syntax for type uuid: "undefined" | O tipo que o Postgres esperava e o valor exato que recebeu |
details, hint | null | O Postgres não acrescenta mais nada para este erro |
| Status HTTP | 400 Bad Request | Como o PostgREST, a parte do Supabase que transforma chamadas supabase.from(...) em SQL, responde a qualquer código sem status próprio (o código dele) |
Leia o que está entre as aspas
O valor citado é a metade útil da mensagem. Ele é exatamente o que chegou ao banco, e cada forma que ele assume aponta para uma linha diferente do seu app.
| Entre as aspas | O que seu app enviou | Onde procurar |
|---|---|---|
"undefined" | uma variável que ainda não tinha valor | uma consulta que rodou antes de o usuário ou o id da página carregar; as duas próximas seções |
"" | uma string vazia | um valor que começa como '' no estado do seu app, ou um campo de formulário deixado em branco |
"null" | null, passado para .eq() | .eq() também escreve null como palavra. Para achar linhas em que a coluna está vazia, a documentação do supabase-js diz "use .is() instead" |
"[object Object]" | um objeto inteiro no lugar do id dele | user passado no lugar de user.id. O JavaScript escreve esse texto para qualquer objeto transformado em string |
"user_…" | um id de um serviço de login que não é o do Supabase | Os ids do Clerk começam com user_ e não são uuids. A seção sobre trocar a coluna trata desse caso |
| um número ou um nome | parte do endereço da página | uma URL como /projects/42 ou /projects/my-first-app, lida como id |
Se o erro aparece em toda visita, procure um nome que não guarda nada. Se aparece só na primeira vez que a página é desenhada, procure um valor que ainda não tinha carregado.
Por que o supabase-js envia a palavra undefined?
Porque ele escreve cada filtro como texto no endereço da requisição, e o
JavaScript escreve um valor que falta como a palavra undefined.
Uma chamada como .eq('user_id', user?.id) vira ?user_id=eq. seguido do
valor. O
código-fonte da biblioteca
monta isso a partir de um modelo, `eq.${value}`, e um comentário mais abaixo
no mesmo arquivo diz o que isso faz com um valor que falta: ele iria "show up as
?column=eq.undefined". O PostgREST lê tudo o que vem depois de eq. como texto
puro, e
o parser dele
só trata null como palavra-chave depois de is.. O Postgres recebe as nove
letras de undefined e tenta lê-las como um uuid.
.rpc() faz o contrário. Os argumentos dele viajam como JSON, que deixa de fora
por completo um valor undefined, então uma chamada de função perde o argumento em
vez de enviar a palavra, e o Supabase responde
Could not find the function.
Num app React, procure o valor que falta em três lugares:
- O usuário ainda não carregou. O Supabase lê a sessão logada de forma
assíncrona (
await supabase.auth.getSession()), então na primeira vez que sua página é desenhada, umuserguardado no estado ainda está vazio euser?.idéundefined. Uma consulta disparada nesse primeiro desenho envia isso. - O id tem outro nome. Uma rota escrita como
/projects/:projectIdpõe o valor emprojectId, e uma página que lêidrecebeundefinedem toda visita. - O id vem de dados ainda a caminho. Um id tirado do resultado de outra consulta, lido antes de essa consulta responder.
Segure a consulta até o id existir
Confira o id antes da chamada e deixe a página mostrar o estado de carregamento
até ele chegar. Para "undefined" e "", essa é a correção inteira.
useEffect(() => {
if (!user?.id) return
supabase
.from('projects')
.select('*')
.eq('user_id', user.id)
.then(({ data, error }) => {
if (!error) setProjects(data)
})
}, [user?.id])
O return não envia nada enquanto falta o id, e user?.id na lista do final
roda o efeito de novo assim que o id aparece. Na máquina de triagem, é esperar o
código estar na etiqueta antes de pôr o pacote na esteira.
Se seu app busca dados com TanStack Query (o hook useQuery), a opção enabled
dele é a mesma trava.
O guia de consultas dependentes
usa enabled: !!userId com a nota "The query will not execute until the userId
exists".
const { data: projects } = useQuery({
queryKey: ['projects', user?.id],
queryFn: async () => {
const { data, error } = await supabase
.from('projects')
.select('*')
.eq('user_id', user.id)
if (error) throw error
return data
},
enabled: !!user?.id,
})
Se você constrói por prompts, peça isso numa frase. Deixe em inglês, a língua em que essas ferramentas raciocinam: "Don't run the projects query until user.id exists, and show the loading state until then."
Vale trocar a coluna para text?
Não, a não ser que os ids que você guarda não sejam uuids de jeito nenhum. Trocar a coluna, ou convertê-la para text numa consulta, tira a checagem que gerou o erro e deixa o id que falta onde estava.
Acompanhe a mesma requisição depois da troca. user_id=eq.undefined agora é uma
pergunta válida, então o Postgres compara a palavra undefined com cada id da
coluna, não encontra nenhum e devolve uma lista vazia. Sua página mostra "Nenhum
projeto ainda" para alguém que tem dez, e no console não há nada que explique. Na
escrita vai além: a linha é gravada com undefined onde fica o id do dono, e uma
consulta pelo dono de verdade nunca a encontra. Na máquina de triagem, é
desligar o leitor de etiquetas. O pacote passa, não bate com nenhum endereço e
nunca é entregue.
O único caso em que o que se troca é a coluna é a linha "user_…" da tabela
acima. Os ids do Clerk são texto, e
o próprio guia do Clerk para Supabase
cria a coluna como user_id text not null default auth.jwt()->>'sub'. É outro
problema: a tabela foi feita para os ids do Supabase e está recebendo os de outro
serviço.
É o id do seu filtro que mantém as linhas dos outros de fora?
Não. O filtro decide quais linhas sua página pede. Quais linhas um visitante pode ler quem decide é a Row Level Security, as regras por linha que o Supabase confere em cada requisição feita com sua chave pública, e qualquer pessoa pode reenviar sua requisição com outro id ou sem nenhum.
Um método do supabase-js mostra a diferença. .match() recebe várias colunas de
uma vez, e
o código-fonte dele
tira qualquer coluna cujo valor seja undefined antes de montar a requisição. Era
para isso aquele comentário sobre eq.undefined. Então um
.match({ user_id: user?.id }) feito antes de o usuário carregar não envia
filtro nenhum, não gera erro nenhum e devolve todas as linhas que as regras da
tabela deixam esse visitante ler. Acrescente .single() a essa chamada e ela
falha de outro jeito, com
JSON object requested, multiple (or no) rows returned,
porque voltou mais de uma linha. Com as regras ligadas, essas linhas continuam
sendo só as do visitante. Com elas desligadas, são a tabela inteira, e
ligar as regras em toda tabela
explica o interruptor e a primeira regra.
Dá para ver de fora se uma tabela entrega linhas a um estranho. Nosso scan gratuito lê seu app no ar com a mesma chave pública que um visitante recebe, informa quais tabelas respondem com linhas e não pede conta.
Antes de mudar uma coluna que seu app lê
Guarde uma cópia do banco de antes de mudar o tipo de uma coluna, e fique sabendo quando uma tabela começar a responder a estranhos.
Reeve Care guarda uma cópia do seu banco no Supabase:
- uma cópia criptografada num cronograma, no armazenamento próprio da Reeve na UE, fora da sua conta do Supabase
- cada tabela com as linhas como estavam antes de você mudar o tipo de uma coluna
- cada cópia verificada antes de valer, contando as linhas de cada tabela
- seus arquivos enviados também, 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
Reeve Monitor continua conferindo de fora:
- se seu app está no ar, conferido a cada minuto
- 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 começa a responder a estranhos depois de um deploy chegue até você sem você ter que ir olhar
- um relatório mensal, em linguagem simples, do que ele viu
O que fazer agora
O que fazer
- Leia o valor entre as aspas. Ele é exatamente o que seu app enviou.
- Para
"undefined"ou"", segure a consulta até o id existir:if (!user?.id) return, ouenabled: !!user?.idno TanStack Query. - Para
"null", encontre valores vazios com.is('column', null). - Para
"[object Object]", passe oiddo objeto. - Mantenha a coluna como
uuide troque paratextsó quando os ids vierem de um serviço que não emite uuids, como o Clerk. - Se uma chamada
.match()com um valor faltando devolver linhas de outras pessoas, ligue a Row Level Security nessa tabela.
Comece pelas aspas, porque o valor dentro delas aponta para a linha do seu app que o enviou. Se a correção que propõem a você muda o tipo de uma coluna, guarde uma cópia do banco de antes da mudança.
Perguntas frequentes
O que significa invalid input syntax for type uuid?
Significa que o Postgres tinha de ler um valor como uuid e o valor não está escrito como um, então ele recusou a requisição antes de ler ou mudar qualquer coisa. O código é 22P02, um app Supabase o recebe como HTTP 400, e o texto entre aspas no fim da mensagem é exatamente o que seu app enviou.
Por que meu id está undefined?
Porque a consulta rodou antes de o valor existir, ou porque ela lê um nome que não guarda nada. Na primeira vez que uma página é desenhada, o usuário logado pode ainda não ter sido lido, então user?.id é undefined, e uma rota que chama o valor de projectId entrega undefined, sempre, a uma página que lê id. O supabase-js escreve o valor que falta na requisição como a palavra undefined.
Devo trocar a coluna para text ou converter o valor?
Não para corrigir isso. Uma coluna text aceita a palavra undefined, compara com cada id, não encontra nenhum, e sua página mostra uma lista vazia sem erro nenhum. Troque a coluna só quando os ids que você guarda não são uuids, como os ids de usuário do Clerk, que o próprio guia do Clerk para Supabase guarda numa coluna text.
Por que só acontece no primeiro carregamento?
Porque no desenho seguinte o id já chegou e a mesma consulta funciona. A página pode se preencher um instante depois e parecer certa, enquanto cada visita ainda envia uma requisição com falha e undefined dentro. Segure a consulta até o id existir e essa requisição nunca é enviada.
Como impeço a consulta de rodar cedo demais?
Saia antes da chamada enquanto o id faltar, por exemplo com if (!user?.id) return no topo do efeito, e coloque user?.id como dependência do efeito para ele rodar de novo quando o id aparecer. Com TanStack Query, use enabled: !!user?.id. Enquanto isso, mostre seu estado de carregamento.