Sicherheitsgrundlagen
Supabase PGRST204: "Could not find the column in the schema cache"
Supabase "Could not find the column in the schema cache" heißt meist: Der Name passt nicht. Vier Prüfungen und der Einzeiler zum Neuladen.

Kurz gesagt
- Supabase "Could not find the column in the schema cache" (PGRST204) heißt: Deine App wollte ein Feld speichern, das die Tabelle nicht führt. PostgREST prüft jeden Schlüssel eines Insert oder Update gegen seine eigene Kopie deines Schemas, Buchstabe für Buchstabe.
- Die übliche Ursache ist der Name: phoneNumber an eine Spalte namens phone_number geschickt, oder ein Schlüssel, der nie eine Spalte war, etwa verknüpfte Zeilen, die in einem Insert verschachtelt sind.
- Supabase lädt diese Kopie von selbst neu, sobald sich eine Tabelle ändert. Ist sie wirklich veraltet, lädt NOTIFY pgrst, 'reload schema'; im SQL Editor sie neu, während die App weiterläuft.
Du hast deinen Builder gebeten, dem Profilformular eine Telefonnummer hinzuzufügen. Die Spalte ist im Table Editor zu sehen. Du drückst auf Speichern, nichts passiert, und die Browserkonsole zeigt das hier:
{
"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" heißt, dass deine App
ein Feld speichern wollte, das die Tabelle, so wie PostgREST sie sieht, nicht hat.
PostgREST ist der Teil von Supabase, der die Aufrufe supabase.from(...) deiner
App in Datenbankabfragen übersetzt. Es führt eine eigene Liste aller Tabellen und
Spalten, den Schema-Cache, damit es die Datenbank nicht bei jeder Anfrage nach
ihrem Aufbau fragen muss.
Hier liegt der Teil, den die meisten Antworten falsch machen: Sie sagen dir, du sollst den Schema-Cache neu laden. Bei Supabase lädt sich der Cache jedes Mal selbst neu, wenn sich eine Tabelle ändert. Wenn du also gerade eine Spalte angelegt hast und diesen Fehler trotzdem siehst, ist der Cache der unwahrscheinlichste Grund. Meistens ist es der Name.
Was bedeutet "Could not find the column in the schema cache"?
Ein Schlüssel in einem Schreibvorgang deiner App hat keine passende Spalte in dieser Tabelle.
Stell dir den Schema-Cache als Hinweistafel im Foyer eines Gebäudes vor: eine Zeile für jedes Büro, also jede Tabelle, und darunter jeder Raum, also jede Spalte. Jede Anfrage deiner App ist ein Lieferschein. PostgREST liest jede Zeile des Lieferscheins gegen die Tafel, und ein Raum, den die Tafel nicht führt, wird schon am Empfang abgewiesen, bevor irgendetwas deine Daten erreicht.
Die Meldung nennt beide Seiten dieses Vergleichs. In Could not find the 'phoneNumber' column of 'profiles' in the schema cache ist phoneNumber das,
was deine App geschickt hat, und profiles die Tabelle, an die es ging. Drei
weitere Dinge stehen fest im
eigenen Code von PostgREST:
- der Code ist
PGRST204und der Status HTTP 400, die Anfrage selbst war also falsch - er kommt nur von Schreibvorgängen:
.insert(),.update()und.upsert(). Ein Lesevorgang, der eine fehlende Spalte nennt, scheitert mit einem anderen Fehler, den Postgres selbst schreibt - er bringt keinen Hinweis mit. Bei einer
fehlenden Tabelle schlägt PostgREST
den nächstliegenden Namen vor, den es kennt; bei einer fehlenden Spalte ist
hintgleichnull, und den Vergleich machst du
Jeder Schlüssel im Objekt, das du einem dieser drei Aufrufe übergibst, muss eine Spalte dieser Tabelle sein, so geschrieben, wie die Tabelle sie schreibt.
Ursache 1: Der Name passt nicht, Buchstabe für Buchstabe
Die Prüfung ist exakt. Für PostgREST sind phoneNumber, phone_number und
phonenumber drei verschiedene Spalten, und deine Tabelle hat höchstens eine
davon.
Es gibt zwei übliche Wege hierher. Der erste sind zwei Benennungsgewohnheiten,
die aufeinandertreffen: JavaScript schreibt ein Feld meist als phoneNumber, eine
Postgres-Spalte heißt meist phone_number. Der Code, den dein Builder geschrieben
hat, und das SQL, das er ausgeführt hat, können jeweils für sich stimmen und sich
trotzdem widersprechen.
Der zweite ist die Falle mit den Anführungszeichen. In SQL wird ein Name ohne doppelte Anführungszeichen in Kleinbuchstaben umgewandelt. Die Dokumentation von Postgres sagt es klar: "Quoting an identifier also makes it case-sensitive, whereas unquoted names are always folded to lower case."
| Was du im SQL Editor ausgeführt hast | Die Spalte, die entstand | Was deine App schicken muss |
|---|---|---|
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 |
Die mittlere Zeile ist die, an der Leute hängen bleiben. Das SQL sieht aus, als
hätte es phoneNumber angelegt, der Code schickt phoneNumber, und die Spalte
heißt phonenumber.
Um die echten Namen zu sehen, öffne die Tabelle im Table Editor und lies die Spaltenköpfe, oder führ das hier im SQL Editor aus:
select column_name
from information_schema.columns
where table_schema = 'public' and table_name = 'profiles';
Dann sieh dir an, was deine App geschickt hat. Öffne in den Entwicklertools
deines Browsers den Tab Netzwerk (Network), klick die fehlgeschlagene Anfrage an
/rest/v1/profiles an und lies ihren Payload.
Die sicherere Lösung ist, die App zu ändern, weil das nichts an deinen Daten
berührt. Bitte deinen Builder, überall dort, wo dieses Feld gespeichert wird, den
exakten Namen der Spalte zu verwenden: "die Spalte in profiles heißt
phone_number; nutze diesen Namen in jedem Insert und Update". Die Spalte
stattdessen umzubenennen ist eine Änderung an einer Tabelle im laufenden Betrieb,
und jeder andere Bildschirm, der noch den alten Namen nutzt, bricht beim ersten
Aufruf.
Wenn du mit Lovable baust, geh den Chat zurück und such nach einer Datenbankänderung, die du freigeben solltest. Lovables Supabase-Dokumentation sagt, es "writes the SQL, shows it to you, and asks for your approval in the project chat before running it". Bis du sie freigibst, gibt es die Spalte nicht, egal was der Code erwartet.
Ursache 2: Ein Schlüssel, der nie eine Spalte war
Die Anfrage trägt etwas, das die Tabelle nie speichern sollte: verknüpfte Zeilen, die im Insert verschachtelt sind, oder ein Feld, das das Formular für sich selbst braucht.
Beim verschachtelten Insert erwarten die Leute, dass er funktioniert:
await supabase.from('orders').insert({
customer_id: user.id,
total: 42,
order_items: [{ product_id: 7, quantity: 2 }],
})
Das scheitert mit Could not find the 'order_items' column of 'orders' in the schema cache. Dieselbe Form zu lesen funktioniert:
.select('*, order_items(*)') liefert eine Bestellung samt ihren Positionen, also
sieht es so aus, als müsste auch der umgekehrte Weg gehen. PostgREST liest jeden
Schlüssel eines Schreibvorgangs als Spalte der einen Tabelle, für die die Anfrage
gilt. Ein Supabase-Nutzer
meldete es als Bug, und die
Antwort, bevor das Issue am 18. Oktober 2026 geschlossen wurde, lautete, dass
verschachtelte Inserts eine fehlende Funktion sind. Der Wunsch danach steht seit
Februar 2017 offen im
Tracker von PostgREST.
Zwei weitere Formen desselben Problems:
- eine Zeile, die zusammen mit ihren verknüpften Zeilen gelesen, in einem
Formular bearbeitet und als Ganzes per
.update()zurückgeschickt wird, sodass das angehängteorder_itemsoderprofilesmitgeht - der gesamte Zustand eines Formulars, direkt an
.insert()übergeben, mit Feldern, die das Formular braucht und die Tabelle nicht, etwaconfirmPassword
Die Lösung: Schreib jede Tabelle für sich, die Elterntabelle zuerst. Leg die
Bestellung an, lass dir ihre id mit .select('id').single() zurückgeben und leg
dann die Positionen mit dieser order_id an. Das sind zwei Anfragen, wenn also
die zweite scheitert, ist die Bestellung trotzdem ohne ihre Positionen
gespeichert. Wenn das nie passieren darf, pack beide Inserts in eine
Postgres-Funktion und ruf sie mit .rpc() auf, was die Antwort in jenem Issue
das übliche Muster nennt. Dann wird alles gespeichert oder nichts. Antwortet
dieser Aufruf stattdessen mit PGRST202, erklärt
die Funktionsversion dieses Fehlers, warum.
Ursache 3: Die Änderung ging in ein anderes Projekt
Die Spalte existiert, in einer Datenbank, mit der deine App nie spricht. Ein Supabase-Konto kann mehrere Projekte enthalten, und ein Branch ist ein eigenes Projekt mit eigener Datenbank. Eine Spalte, die im SQL Editor des falschen angelegt wird, ändert eine Tafel in einem anderen Gebäude.
Im Issue #42183 von Supabase, einer Meldung, der Cache aktualisiere sich nicht ("does not update"), egal was man versuche, antwortete einer der Entwickler von PostgREST, dass die Meldung zwei verschiedene Projektreferenzen nannte, und bat darum zu bestätigen, dass die Änderung in dem Projekt gemacht wurde, an das die Anfragen gingen.
Die Prüfung braucht zwei Adressen. Deine App ruft
https://<reference>.supabase.co auf, was du bei jeder Anfrage im Tab Netzwerk
siehst. Das Dashboard zeigt das Projekt, in dem du bist, unter
supabase.com/dashboard/project/<reference>. Die beiden Referenzen müssen
dieselbe Zeichenkette sein.
Welches Projekt ist meins erklärt, wie
du es findest, wenn jemand anderes Supabase eingerichtet hat.
Ursache 4: Der Cache ist wirklich veraltet
Das ist bei Supabase der seltene Fall, weil Supabase den Cache für dich neu lädt.
Jede Supabase-Datenbank wird mit einem Event-Trigger namens pgrst_ddl_watch
eingerichtet, der nach jeder Änderung am Aufbau der Datenbank läuft, ALTER TABLE
eingeschlossen, und NOTIFY pgrst, 'reload schema' sendet: die Nachricht, die
PostgREST sagt, seine Liste neu aufzubauen. Du kannst ihn im
Datenbank-Setup von Supabase
nachlesen. Eine Spalte im Table Editor oder im SQL Editor anzulegen, beschriftet
die Tafel von selbst neu.
Schief geht es, wenn diese Nachricht nie ankommt. Supabases
Anleitung zur Fehlersuche
beschreibt den Fall: "an underlying Postgres notification queue issue prevents
PostgREST from receiving cache reload signals after schema changes". Das Log von
PostgREST zeigt dann Failed listening for database notifications on the "pgrst" channel, und die Tafel bleibt, wie sie war, egal wie viele Spalten du anlegst.
Wie lade ich den Supabase-Schema-Cache neu?
Führ eine Zeile im SQL Editor aus:
NOTIFY pgrst, 'reload schema';
Supabases Seite dazu sagt, du solltest unter Results "Success. No rows returned" sehen. Das ist bei einer laufenden App unbedenklich. Die Dokumentation von PostgREST sagt "Requests will wait until the schema cache reload is done", also sieht niemand, der die App gerade nutzt, währenddessen einen Fehler.
Ist die Benachrichtigungswarteschlange das Problem, bleibt die Nachricht zum Neuladen genauso hängen wie die automatische. Die Anleitung zur Fehlersuche nennt dafür eine zweite Zeile:
select pg_notification_queue_usage();
Laut Supabase bringt sie PostgREST dazu, seinen Cache zu aktualisieren, und "does not require a database instance or PostgREST service restart". Dann versuch das Speichern noch einmal.
Wenn Neuladen nicht hilft
Starte das Projekt neu, und wenn der Fehler auch das übersteht, geh zurück zu den Ursachen 1 bis 3.
Ein Neustart gibt PostgREST immer eine frische Liste. Derselbe Entwickler von PostgREST schrieb in #42183, dass ein neu gestartetes PostgREST "won't go live if it doesn't load a new schema cache". Restart project findest du unter Project Settings, dann General, und wie man ein Supabase-Projekt neu startet führt dich durch, samt der paar Minuten, die es offline ist.
Ein Fehler, der einen Neustart übersteht, kommt also von einer frischen, genauen Liste: Diese Tabelle in diesem Projekt hat keine Spalte mit diesem Namen. #42183 selbst, im Januar 2026 von jemandem eröffnet, der Neuladen, Neustart und das Löschen und Neuanlegen der Spalte versucht hatte, war noch offen, als wir es am 21. Oktober 2026 gelesen haben. Die Antwort des Entwicklers dort schlug außerdem vor, in den Infrastructure-Einstellungen des Projekts auf PostgREST 14 zu aktualisieren.
Streich Löschen und Neuanlegen von deiner Liste. alter table … drop column
löscht jeden Wert, der in dieser Spalte gespeichert ist, und die neue Spalte mit
demselben Namen beginnt leer. Wenn du gleich eine Tabelle ändern willst, in die
echte Nutzer schreiben, behalte eine Kopie der Datenbank von vor der Änderung.
Reeve Care legt eine nach Zeitplan an und bewahrt sie
verschlüsselt auf Reeves eigenem Speicher in der EU auf.
Deine generierten Typen auf Stand halten
Generiere deine TypeScript-Typen nach jeder Änderung an einer Tabelle neu, dann kann ein falsch geschriebener Spaltenname schon im Editor auffallen, bevor er die Live-Seite erreicht.
Supabase baut die Datei aus deinen echten Spalten, entweder im Dashboard oder mit einem Befehl aus seiner Anleitung zum Generieren von Typen:
npx supabase gen types typescript --project-id "$PROJECT_REF" --schema public > database.types.ts
Wird der Client mit diesen Typen erstellt, createClient<Database>(…), führt der
Typ Insert jeder Tabelle die Spalten auf, die .insert() annimmt. Eine falsch
geschriebene Spalte in einem Objekt, das direkt im Aufruf ausgeschrieben ist,
wird dann als Fehler markiert, bevor du veröffentlichst. Laut Lovables
Dokumentation "regenerates the TypeScript types your app uses" es nach einer
Datenbankänderung. Eine Typdatei, die vor deiner letzten Änderung an der Tabelle
generiert wurde, führt noch die alten Spalten.
Eine Tabelle im laufenden Betrieb ändern, ohne zu verlieren, was drinsteht
Behalte eine Kopie von vor der Änderung und wisse sofort, ob die App danach noch antwortet.
Reeve Care bewahrt eine Kopie deiner Supabase-Datenbank auf:
- eine verschlüsselte Kopie nach Zeitplan, auf Reeves eigenem Speicher in der EU, außerhalb deines Supabase-Kontos
- die letzte Kopie vor deiner Änderung enthält die Tabellen und Spalten, wie sie waren, samt Zeilen
- jede Kopie wird geprüft, bevor sie zählt, indem die Zeilen in jeder Tabelle gezählt werden
- auch deine hochgeladenen Dateien, sobald du eine Storage-Zugangsberechtigung verbindest
- Wiederherstellung mit einem Klick, vorher wird eine Sicherheitskopie des aktuellen Stands angelegt
- alles, was Monitor macht
Reeve Monitor prüft weiter von außen:
- ob deine App erreichbar ist, jede Minute geprüft, sodass ein kaputter Speichern-Knopf und ein Ausfall leicht auseinanderzuhalten sind
- der vollständige Sicherheitscheck jede Stunde, für bis zu drei Apps
- eine Nachricht, wenn sich ein Ergebnis ändert, sodass ein Deploy, der ändert, was deine App preisgibt, dich erreicht, ohne dass du nachsehen musst
- ein monatlicher Bericht in klarer Sprache über das, was er gesehen hat
Was du jetzt tun solltest
Was zu tun ist
- Lies die zwei Namen in der Meldung: den Schlüssel, den deine App geschickt hat, und die Tabelle, an die er ging.
- Vergleich diesen Schlüssel Buchstabe für Buchstabe mit der Spalte im Table Editor. Ein Name, der in SQL ohne Anführungszeichen geschrieben wurde, wird in Kleinbuchstaben gespeichert.
- Such nach Schlüsseln, die keine Spalten sind: verknüpfte Zeilen, die in einem Insert verschachtelt sind, oder eine ganze Zeile, die mit ihren verknüpften Zeilen zurückgeschickt wird. Schreib jede Tabelle für sich, oder pack beide Schreibvorgänge in eine Funktion.
- Prüf, dass die Referenz in der Supabase-Adresse deiner App zu dem Projekt passt, das du geändert hast.
- Passt das alles, führ
NOTIFY pgrst, 'reload schema';aus, dannselect pg_notification_queue_usage();, und starte das Projekt nur neu, wenn keins von beiden hilft. - Generiere deine TypeScript-Typen nach jeder Änderung an einer Tabelle neu.
Fang mit dem Spaltennamen an, den die Meldung zitiert, denn er ist genau das, was deine App geschickt hat, und der Table Editor zeigt genau das, was die Tabelle hat. Bevor du in einer Tabelle, die Leute nutzen, etwas umbenennst oder löschst, behalte eine Kopie der Datenbank von vor der Änderung.
FAQ
Was bedeutet PGRST204?
PGRST204 ist der Code, den PostgREST, der Teil von Supabase, der die Datenbankaufrufe deiner App beantwortet, einem Schreibvorgang gibt, der eine Spalte nennt, die die Tabelle nicht hat. Er kommt mit HTTP 400 und der Meldung "Could not find the 'x' column of 'y' in the schema cache" zurück, wobei x der Schlüssel ist, den deine App geschickt hat, und y die Tabelle. Inserts, Updates und Upserts lösen ihn aus.
Ich habe die Spalte angelegt und es schlägt trotzdem fehl. Warum?
Meist liegt es am Namen. Prüf, dass deine App genau den gespeicherten Namen schickt (eine Spalte, die in SQL ohne Anführungszeichen als phoneNumber angelegt wurde, heißt gespeichert phonenumber), dass du sie in dem Projekt angelegt hast, das deine App aufruft, und dass sonst nichts in der Anfrage ein Schlüssel ist, den die Tabelle nicht kennt. Supabase lädt den Schema-Cache nach jedem ALTER TABLE von selbst neu, ein veralteter Cache ist also das Letzte, was du verdächtigen solltest.
Wie lade ich den Supabase-Schema-Cache neu?
Führ NOTIFY pgrst, 'reload schema'; im SQL Editor aus. Ändert das nichts, nennt Supabases Anleitung zur Fehlersuche einen zweiten Befehl, select pg_notification_queue_usage();, für den Fall, dass die Nachrichten zum Neuladen feststecken. Anfragen, die während des Neuladens ankommen, warten, bis es fertig ist.
Leert das Pausieren des Projekts den Cache?
Ja. PostgREST baut bei jedem Start einen frischen Schema-Cache, und ein pausiertes Projekt startet es beim Fortsetzen neu. Restart project unter Project Settings und dann General macht dasselbe ohne Pause. Übersteht der Fehler beides, lag der Cache die ganze Zeit richtig: Die Spalte fehlt in dieser Tabelle in diesem Projekt.
Warum scheitern verschachtelte Inserts mit diesem Fehler?
Weil PostgREST jeden Schlüssel eines Insert als Spalte einer einzigen Tabelle liest, und ein Schlüssel mit verknüpften Zeilen ist eine Spalte, die es nicht findet. Verknüpfte Zeilen in einer Anfrage zu lesen funktioniert, sie so zu schreiben wurde nie unterstützt. Leg zuerst den Elterndatensatz an und dann die Kinder mit seiner id, oder erledige beides in einer Postgres-Funktion, die du mit rpc() aufrufst.