Basi della sicurezza
Supabase PGRST204: "Could not find the column in the schema cache"
Supabase "Could not find the column in the schema cache" di solito è un nome che non torna. Quattro controlli in ordine e la riga che ricarica.

In breve
- Supabase "Could not find the column in the schema cache" (PGRST204) significa che la tua app ha provato a salvare un campo che la tabella non elenca. PostgREST confronta ogni chiave di un insert o di un update con la sua copia del tuo schema, lettera per lettera.
- La causa abituale è il nome: phoneNumber inviato a una colonna chiamata phone_number, oppure una chiave che non è mai stata una colonna, come righe collegate annidate dentro un insert.
- Supabase ricarica quella copia da solo ogni volta che una tabella cambia. Quando è davvero vecchia, NOTIFY pgrst, 'reload schema'; nel SQL Editor la ricarica mentre l'app continua a funzionare.
Hai chiesto al tuo builder di aggiungere un numero di telefono al modulo del profilo. La colonna c'è nel Table Editor. Premi Salva, non succede niente, e la console del browser mostra questo:
{
"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 che la tua
app ha provato a salvare un campo che la tabella, per come la vede PostgREST, non
ha. PostgREST è la parte di Supabase che trasforma le chiamate
supabase.from(...) della tua app in query al database. Tiene un suo elenco di
ogni tabella e ogni colonna, chiamato schema cache, per non dover chiedere al
database la sua forma a ogni richiesta.
Ecco la parte che quasi tutte le risposte sbagliano: ti dicono di ricaricare la schema cache. Su Supabase la cache si ricarica da sola ogni volta che una tabella cambia. Quindi se hai appena aggiunto una colonna e vedi ancora questo errore, la cache è il motivo meno probabile. Di solito è il nome.
Cosa significa "Could not find the column in the schema cache"?
Una chiave di una scrittura inviata dalla tua app non ha una colonna corrispondente in quella tabella.
Immagina la schema cache come il tabellone nell'atrio di un palazzo: una riga per ogni ufficio, cioè ogni tabella, e sotto ogni stanza, cioè ogni colonna. Ogni richiesta della tua app è una bolla di consegna. PostgREST legge ogni riga della bolla confrontandola con il tabellone, e una stanza che il tabellone non elenca viene respinta alla reception, prima che qualcosa raggiunga i tuoi dati.
Il messaggio nomina entrambi i lati di questo confronto. In Could not find the 'phoneNumber' column of 'profiles' in the schema cache, phoneNumber è ciò che
la tua app ha inviato e profiles la tabella a cui era diretto. Altre tre cose
sono fissate nel
codice stesso di PostgREST:
- il codice è
PGRST204e lo stato HTTP 400, cioè la richiesta stessa era sbagliata - arriva solo dalle scritture:
.insert(),.update()e.upsert(). Una lettura che nomina una colonna inesistente fallisce con un altro errore, scritto da Postgres - non porta nessun suggerimento. Per una
tabella mancante PostgREST propone
il nome più vicino che conosce; per una colonna mancante
hintènull, e il confronto tocca a te
Ogni chiave dell'oggetto che passi a una di queste tre chiamate deve essere una colonna di quella tabella, scritta come la scrive la tabella.
Causa 1: il nome non coincide, lettera per lettera
Il controllo è esatto. Per PostgREST phoneNumber, phone_number e
phonenumber sono tre colonne diverse, e la tua tabella ne ha al massimo una.
Ci sono due modi comuni per arrivarci. Il primo sono due abitudini di nomi che
si incontrano: JavaScript di solito scrive un campo come phoneNumber, e una
colonna Postgres di solito si chiama phone_number. Il codice scritto dal tuo
builder e l'SQL che ha eseguito possono essere giusti ciascuno per conto suo e
non coincidere lo stesso.
Il secondo è la trappola delle virgolette. In SQL, un nome scritto senza virgolette doppie viene convertito in minuscolo. La documentazione di Postgres lo dice chiaramente: "Quoting an identifier also makes it case-sensitive, whereas unquoted names are always folded to lower case."
| Cosa hai eseguito nel SQL Editor | La colonna creata | Cosa deve inviare la tua app |
|---|---|---|
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 |
La riga di mezzo è quella che trae in inganno. L'SQL sembra aver creato
phoneNumber, il codice invia phoneNumber, e la colonna è phonenumber.
Per vedere i nomi veri, apri la tabella nel Table Editor e leggi le intestazioni delle colonne, oppure esegui questo nel SQL Editor:
select column_name
from information_schema.columns
where table_schema = 'public' and table_name = 'profiles';
Poi guarda cosa ha inviato la tua app. Negli strumenti per sviluppatori del
browser apri la scheda Rete (Network), clicca sulla richiesta fallita verso
/rest/v1/profiles e leggi il suo payload.
La correzione più sicura è cambiare l'app, perché non tocca nulla dei tuoi dati.
Chiedi al tuo builder di usare il nome esatto della colonna ovunque quel campo
venga salvato: "la colonna in profiles è phone_number; usa quel nome in ogni
insert e update". Rinominare invece la colonna è una modifica a una tabella in
uso, e ogni altra schermata che usa ancora il vecchio nome si rompe la prima
volta che parte.
Se costruisci con Lovable, scorri la chat cercando una modifica al database che ti era stato chiesto di approvare. La documentazione Supabase di Lovable dice che "writes the SQL, shows it to you, and asks for your approval in the project chat before running it". Finché non la approvi, la colonna non esiste, qualunque cosa si aspetti il codice.
Causa 2: una chiave che non è mai stata una colonna
La richiesta porta qualcosa che la tabella non doveva mai salvare: righe collegate annidate nell'insert, oppure un campo che il modulo tiene per sé.
L'insert annidato è quello che tutti si aspettano funzioni:
await supabase.from('orders').insert({
customer_id: user.id,
total: 42,
order_items: [{ product_id: 7, quantity: 2 }],
})
Fallisce con Could not find the 'order_items' column of 'orders' in the schema cache. Leggere la stessa forma funziona: .select('*, order_items(*)')
restituisce un ordine con i suoi articoli, quindi sembra che anche il contrario
debba funzionare. PostgREST legge ogni chiave di una scrittura come colonna
dell'unica tabella a cui va la richiesta. Un utente di Supabase
l'ha segnalato come bug, e
la risposta, prima che la issue venisse chiusa il 18 ottobre 2026, è stata che
gli insert annidati sono una funzione che manca. La richiesta è aperta sul
tracker di PostgREST da
febbraio 2017.
Altre due forme dello stesso problema:
- una riga letta insieme alle sue righe collegate, modificata in un modulo e
rimandata intera con
.update(), così ilorder_itemso ilprofilesallegato parte con lei - l'intero stato di un modulo passato così com'è a
.insert(), con campi che servono al modulo e non alla tabella, comeconfirmPassword
La correzione è scrivere ogni tabella per conto suo, prima il genitore. Inserisci
l'ordine e fatti restituire il suo id con .select('id').single(), poi inserisci
gli articoli con quel order_id. Sono due richieste, quindi se la seconda
fallisce l'ordine resta salvato senza i suoi articoli. Quando non deve succedere
mai, metti i due insert in una funzione Postgres e chiamala con .rpc(), che la
risposta in quella issue chiama lo schema standard. Così si salva tutto o niente.
Se invece quella chiamata risponde con PGRST202,
la versione di questo errore per le funzioni
spiega perché.
Causa 3: la modifica è finita in un altro progetto
La colonna esiste, in un database con cui la tua app non parla mai. Un account Supabase può contenere più progetti, e un branch è un progetto a parte con il suo database. Una colonna aggiunta nel SQL Editor di quello sbagliato cambia il tabellone di un altro palazzo.
Nella issue #42183 di Supabase, una segnalazione secondo cui la cache "does not update" qualunque cosa si provasse, uno degli sviluppatori di PostgREST ha risposto che la segnalazione citava due riferimenti di progetto diversi, e ha chiesto di confermare che la modifica fosse stata fatta nel progetto a cui andavano le richieste.
Il controllo richiede due indirizzi. La tua app chiama
https://<reference>.supabase.co, che vedi su qualsiasi richiesta nella scheda
Rete. La dashboard mostra il progetto in cui sei su
supabase.com/dashboard/project/<reference>. I due riferimenti devono essere la
stessa stringa.
Qual è il mio progetto spiega come
trovarlo quando Supabase l'ha configurato qualcun altro.
Causa 4: la cache è davvero vecchia
È il caso raro su Supabase, perché Supabase ricarica la cache per te. Ogni
database Supabase viene configurato con un event trigger, pgrst_ddl_watch, che
gira dopo ogni modifica alla forma del database, ALTER TABLE compreso, e invia
NOTIFY pgrst, 'reload schema': il messaggio che dice a PostgREST di
ricostruire il suo elenco. Puoi leggerlo nella
configurazione del database di Supabase.
Aggiungere una colonna nel Table Editor o nel SQL Editor ridipinge il tabellone
da solo.
Va storto quando quel messaggio non arriva mai. La
guida alla risoluzione dei problemi
di Supabase descrive il caso: "an underlying Postgres notification queue issue
prevents PostgREST from receiving cache reload signals after schema changes". Il
log di PostgREST mostra allora Failed listening for database notifications on the "pgrst" channel, e il tabellone resta com'era per quante colonne tu
aggiunga.
Come ricarico la schema cache di Supabase?
Esegui una riga nel SQL Editor:
NOTIFY pgrst, 'reload schema';
La pagina di Supabase su questo dice che dovresti vedere "Success. No rows returned" sotto Results. È sicuro su un'app in uso. La documentazione di PostgREST dice "Requests will wait until the schema cache reload is done", quindi nessuno che usa l'app vede un errore nel frattempo.
Se il problema è la coda delle notifiche, il messaggio di ricarica si blocca allo stesso modo di quello automatico. La guida alla risoluzione dei problemi dà una seconda riga per quel caso:
select pg_notification_queue_usage();
Secondo Supabase, spinge PostgREST ad aggiornare la sua cache e "does not require a database instance or PostgREST service restart". Poi riprova a salvare.
Quando ricaricare non basta
Riavvia il progetto, e se l'errore sopravvive anche a quello, torna alle cause da 1 a 3.
Un riavvio dà sempre a PostgREST un elenco nuovo. Lo stesso sviluppatore di PostgREST ha scritto in #42183 che un PostgREST riavviato "won't go live if it doesn't load a new schema cache". Restart project si trova in Project Settings, poi General, e come riavviare un progetto Supabase ti guida, compresi i pochi minuti in cui resta offline.
Quindi un errore che sopravvive a un riavvio viene da un elenco nuovo ed esatto: quella tabella, in quel progetto, non ha una colonna con quel nome. La stessa #42183, aperta a gennaio 2026 da qualcuno che aveva provato una ricarica, un riavvio e la cancellazione e la nuova aggiunta della colonna, era ancora aperta quando l'abbiamo letta il 21 ottobre 2026. La risposta dello sviluppatore lì suggeriva anche di passare a PostgREST 14 dalle impostazioni Infrastructure del progetto.
Cancella dalla lista l'idea di eliminare e riaggiungere. alter table … drop column cancella ogni valore salvato in quella colonna, e la nuova colonna con lo
stesso nome parte vuota. Se stai per modificare una tabella in cui scrivono
utenti veri, tieni una copia del database da prima della modifica.
Reeve Care ne fa una secondo una pianificazione e la
conserva cifrata, sullo spazio di archiviazione di Reeve nell'UE.
Tenere aggiornati i tipi generati
Rigenera i tuoi tipi TypeScript dopo ogni modifica a una tabella, e un nome di colonna scritto male può comparire nell'editor prima di arrivare al sito online.
Supabase costruisce il file dalle tue colonne reali, dalla dashboard oppure con un comando dalla sua guida alla generazione dei tipi:
npx supabase gen types typescript --project-id "$PROJECT_REF" --schema public > database.types.ts
Quando il client viene creato con quei tipi, createClient<Database>(…), il tipo
Insert di ogni tabella elenca le colonne che .insert() accetta. Una colonna
scritta male in un oggetto scritto per intero nella chiamata viene allora
segnata come errore prima che tu pubblichi. La documentazione di Lovable dice che
"regenerates the TypeScript types your app uses" dopo una modifica al database.
Un file di tipi generato prima della tua ultima modifica alla tabella elenca
ancora le vecchie colonne.
Modificare una tabella in uso senza perdere quello che contiene
Tieni una copia da prima della modifica, e sappi subito se l'app risponde ancora dopo.
Reeve Care tiene una copia del tuo database Supabase:
- una copia cifrata secondo una pianificazione, sullo spazio di archiviazione di Reeve nell'UE, fuori dal tuo account Supabase
- l'ultima copia prima della tua modifica contiene tabelle e colonne com'erano, righe comprese
- ogni copia verificata prima di contare, contando le righe di ogni tabella
- anche i file caricati, appena colleghi una credenziale Storage
- un ripristino con un clic, con una copia di sicurezza dello stato attuale presa prima
- tutto quello che fa Monitor
Reeve Monitor continua a controllare dall'esterno:
- se la tua app è raggiungibile, controllato ogni minuto, così un pulsante Salva rotto e un disservizio si distinguono facilmente
- il controllo di sicurezza completo ogni ora, su un massimo di tre app
- un messaggio quando un risultato cambia, così un deploy che cambia ciò che la tua app espone ti raggiunge senza che tu debba andare a guardare
- un report mensile, in linguaggio semplice, di quello che ha visto
Cosa fare adesso
Cosa fare
- Leggi i due nomi nel messaggio: la chiave che la tua app ha inviato e la tabella a cui è andata.
- Confronta quella chiave con la colonna nel Table Editor, lettera per lettera. Un nome scritto in SQL senza virgolette viene salvato in minuscolo.
- Cerca chiavi che non sono colonne: righe collegate annidate in un insert, o una riga intera rimandata con le sue righe collegate. Scrivi ogni tabella per conto suo, oppure metti le due scritture in una funzione.
- Controlla che il riferimento nell'indirizzo Supabase della tua app corrisponda al progetto che hai modificato.
- Se tutto questo torna, esegui
NOTIFY pgrst, 'reload schema';, poiselect pg_notification_queue_usage();, e riavvia il progetto solo se nessuno dei due aiuta. - Rigenera i tuoi tipi TypeScript dopo ogni modifica a una tabella.
Comincia dal nome di colonna che il messaggio cita, perché è esattamente ciò che la tua app ha inviato, e il Table Editor mostra esattamente ciò che la tabella ha. Prima di rinominare o eliminare qualcosa in una tabella che le persone usano, tieni una copia del database da prima della modifica.
FAQ
Cosa significa PGRST204?
PGRST204 è il codice che PostgREST, la parte di Supabase che risponde alle chiamate della tua app al database, dà a una scrittura che nomina una colonna che la tabella non ha. Torna con HTTP 400 e il messaggio "Could not find the 'x' column of 'y' in the schema cache", dove x è la chiave inviata dalla tua app e y la tabella a cui l'ha inviata. Lo producono insert, update e upsert.
Ho aggiunto la colonna e fallisce ancora. Perché?
Di solito per il nome. Controlla che la tua app invii esattamente il nome salvato (una colonna aggiunta in SQL come phoneNumber, senza virgolette, viene salvata come phonenumber), di averla aggiunta nel progetto che la tua app chiama, e che nient'altro nella richiesta sia una chiave che la tabella non ha. Supabase ricarica la schema cache da solo dopo ogni ALTER TABLE, quindi una cache vecchia è l'ultima cosa da sospettare.
Come ricarico la schema cache di Supabase?
Esegui NOTIFY pgrst, 'reload schema'; nel SQL Editor. Se non cambia nulla, la guida alla risoluzione dei problemi di Supabase dà un secondo comando, select pg_notification_queue_usage();, per quando i messaggi di ricarica restano bloccati. Le richieste che arrivano durante la ricarica aspettano che finisca.
Mettere in pausa il progetto la svuota?
Sì. PostgREST costruisce una schema cache nuova ogni volta che parte, e riprendere un progetto in pausa lo fa ripartire. Restart project, in Project Settings e poi General, fa lo stesso senza la pausa. Se l'errore sopravvive a entrambi, la cache aveva ragione fin dall'inizio: a quella tabella, in quel progetto, manca la colonna.
Perché gli insert annidati falliscono con questo errore?
Perché PostgREST legge ogni chiave di un insert come colonna di una sola tabella, e una chiave che contiene righe collegate è una colonna che non trova. Leggere righe collegate in una sola richiesta funziona, scriverle così non è mai stato supportato. Inserisci prima il genitore e poi i figli con il suo id, oppure fai entrambe le cose in una funzione Postgres chiamata con rpc().