Aller au contenu

Bases de la sécurité

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

Supabase "Could not find the column in the schema cache" vient le plus souvent du nom. Quatre vérifications, et la ligne qui recharge le cache.

Vlad Tkachenko12 min de lecture
Un tableau d'affichage de hall avec un emplacement vide dans sa liste de pièces, et devant lui un bon de livraison dont une ligne est éclairée en ambre.

En bref

  • Supabase "Could not find the column in the schema cache" (PGRST204) signifie que votre application a voulu enregistrer un champ que la table ne répertorie pas. PostgREST compare chaque clé d'un insert ou d'un update à sa propre copie de votre schéma, lettre par lettre.
  • La cause habituelle est le nom : phoneNumber envoyé à une colonne appelée phone_number, ou une clé qui n'a jamais été une colonne, comme des lignes liées imbriquées dans un insert.
  • Supabase recharge cette copie tout seul dès qu'une table change. Quand elle est vraiment périmée, NOTIFY pgrst, 'reload schema'; dans le SQL Editor la recharge pendant que l'application continue de tourner.

Vous avez demandé à votre builder d'ajouter un numéro de téléphone au formulaire de profil. La colonne apparaît dans le Table Editor. Vous cliquez sur Enregistrer, rien ne se passe, et la console du navigateur affiche ceci :

{
  "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" signifie que votre application a voulu enregistrer un champ que la table, telle que PostgREST la voit, n'a pas. PostgREST est la partie de Supabase qui transforme les appels supabase.from(...) de votre application en requêtes à la base de données. Il tient sa propre liste de chaque table et de chaque colonne, appelée cache de schéma, pour ne pas avoir à interroger la base sur sa structure à chaque requête.

Voici le point sur lequel la plupart des réponses se trompent : elles vous disent de recharger le cache de schéma. Sur Supabase, le cache se recharge tout seul à chaque fois qu'une table change. Si vous venez d'ajouter une colonne et que vous voyez encore cette erreur, le cache est donc la raison la moins probable. Le nom l'est le plus souvent.

Que signifie "Could not find the column in the schema cache" ?

Une clé d'une écriture envoyée par votre application n'a pas de colonne correspondante dans cette table.

Imaginez le cache de schéma comme le tableau d'affichage dans le hall d'un immeuble : une ligne pour chaque bureau, c'est-à-dire chaque table, et en dessous chaque pièce, c'est-à-dire chaque colonne. Chaque requête de votre application est un bon de livraison. PostgREST lit chaque ligne du bon face au tableau, et une pièce que le tableau ne mentionne pas est refusée à l'accueil, avant que quoi que ce soit n'atteigne vos données.

Le message nomme les deux côtés de cette comparaison. Dans Could not find the 'phoneNumber' column of 'profiles' in the schema cache, phoneNumber est ce que votre application a envoyé et profiles la table à laquelle c'était destiné. Trois autres choses sont fixées dans le code de PostgREST lui-même :

  • le code est PGRST204 et le statut HTTP 400, ce qui veut dire que la requête elle-même était fausse
  • il vient uniquement des écritures : .insert(), .update() et .upsert(). Une lecture qui nomme une colonne absente échoue avec une autre erreur, écrite par Postgres
  • il ne donne aucune piste. Pour une table absente, PostgREST suggère le nom le plus proche qu'il connaît ; pour une colonne absente, hint vaut null, et la comparaison vous revient

Chaque clé de l'objet que vous passez à l'un de ces trois appels doit être une colonne de cette table, écrite comme la table l'écrit.

PostgREST compare chaque clé envoyée par votre application aux colonnes de sa liste, lettre par lettre. phoneNumber et phone_number sont deux noms différents.

Cause 1 : le nom ne correspond pas, lettre par lettre

La vérification est exacte. Pour PostgREST, phoneNumber, phone_number et phonenumber sont trois colonnes différentes, et votre table en a au plus une.

Il y a deux façons courantes d'en arriver là. La première, ce sont deux habitudes de nommage qui se rencontrent : JavaScript écrit généralement un champ phoneNumber, et une colonne Postgres s'appelle généralement phone_number. Le code écrit par votre builder et le SQL qu'il a exécuté peuvent chacun être justes de leur côté et quand même se contredire.

La seconde, c'est le piège des guillemets. En SQL, un nom tapé sans guillemets doubles est converti en minuscules. La documentation de Postgres le dit clairement : "Quoting an identifier also makes it case-sensitive, whereas unquoted names are always folded to lower case."

Ce que vous avez exécuté dans le SQL EditorLa colonne crééeCe que votre application doit envoyer
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

C'est la ligne du milieu qui piège les gens. Le SQL a l'air d'avoir créé phoneNumber, le code envoie phoneNumber, et la colonne s'appelle phonenumber.

Pour voir les vrais noms, ouvrez la table dans le Table Editor et lisez les en-têtes de colonnes, ou exécutez ceci dans le SQL Editor :

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

Regardez ensuite ce que votre application a envoyé. Dans les outils de développement de votre navigateur, ouvrez l'onglet Réseau (Network), cliquez sur la requête échouée vers /rest/v1/profiles et lisez sa charge utile.

La correction la plus sûre est de modifier l'application, parce que cela ne touche à rien dans vos données. Demandez à votre builder d'utiliser le nom exact de la colonne partout où ce champ est enregistré : "la colonne de profiles s'appelle phone_number ; utilise ce nom dans chaque insert et chaque update". Renommer la colonne à la place, c'est modifier une table en service, et tout autre écran qui utilise encore l'ancien nom casse la première fois qu'il s'exécute.

Si vous construisez avec Lovable, remontez le chat à la recherche d'une modification de base de données que vous deviez approuver. La documentation Supabase de Lovable dit qu'il "writes the SQL, shows it to you, and asks for your approval in the project chat before running it". Tant que vous ne l'avez pas approuvée, la colonne n'existe pas, quoi qu'attende le code.

Cause 2 : une clé qui n'a jamais été une colonne

La requête contient quelque chose que la table n'a jamais été censée stocker : des lignes liées imbriquées dans l'insert, ou un champ que le formulaire garde pour lui.

L'insert imbriqué est celui dont on attend qu'il fonctionne :

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

Cela échoue avec Could not find the 'order_items' column of 'orders' in the schema cache. Lire la même forme fonctionne : .select('*, order_items(*)') renvoie une commande avec ses articles, donc le sens inverse a l'air de devoir marcher aussi. PostgREST lit chaque clé d'une écriture comme une colonne de la seule table visée par la requête. Un utilisateur de Supabase l'a signalé comme un bug, et la réponse, avant la fermeture de l'issue le 18 octobre 2026, était que les inserts imbriqués sont une fonctionnalité manquante. La demande est ouverte sur le suivi de PostgREST depuis février 2017.

Deux autres formes du même problème :

  • une ligne lue avec ses lignes liées, modifiée dans un formulaire et renvoyée telle quelle dans .update(), si bien que le order_items ou le profiles attaché part avec elle
  • l'état complet d'un formulaire passé directement à .insert(), avec des champs dont le formulaire a besoin et pas la table, comme confirmPassword

La correction consiste à écrire chaque table séparément, le parent d'abord. Insérez la commande et demandez son id en retour avec .select('id').single(), puis insérez les articles avec ce order_id. Ce sont deux requêtes : si la seconde échoue, la commande reste enregistrée sans ses articles. Quand cela ne doit jamais arriver, mettez les deux inserts dans une fonction Postgres et appelez-la avec .rpc(), ce que la réponse sur cette issue appelle la méthode standard. Alors tout est enregistré, ou rien. Si cet appel répond PGRST202 à la place, la version fonction de cette erreur explique pourquoi.

Un bon imbriqué est lu comme une colonne de plus de la première table. Envoyés séparément, le parent d'abord, chacun trouve son propre tableau.

Cause 3 : la modification est partie dans un autre projet

La colonne existe, dans une base de données à laquelle votre application ne parle jamais. Un compte Supabase peut contenir plusieurs projets, et une branche est un projet à part avec sa propre base. Une colonne ajoutée dans le SQL Editor du mauvais modifie le tableau d'un autre immeuble.

Sur l'issue #42183 de Supabase, un signalement selon lequel le cache "does not update" quoi qu'on essaie, l'un des développeurs de PostgREST a répondu que le signalement citait deux références de projet différentes, et a demandé de confirmer que la modification avait été faite dans le projet auquel les requêtes étaient envoyées.

La vérification tient en deux adresses. Votre application appelle https://<reference>.supabase.co, ce que vous voyez sur n'importe quelle requête dans l'onglet Réseau. Le tableau de bord affiche le projet où vous êtes à l'adresse supabase.com/dashboard/project/<reference>. Les deux références doivent être la même chaîne. Quel projet est le mien explique comment le retrouver quand quelqu'un d'autre a configuré Supabase.

Cause 4 : le cache est vraiment périmé

C'est le cas rare sur Supabase, parce que Supabase recharge le cache pour vous. Chaque base Supabase est configurée avec un déclencheur d'événement, pgrst_ddl_watch, qui s'exécute après chaque changement de structure de la base, ALTER TABLE compris, et envoie NOTIFY pgrst, 'reload schema' : le message qui dit à PostgREST de reconstruire sa liste. Vous pouvez le lire dans la configuration de base de Supabase. Ajouter une colonne dans le Table Editor ou le SQL Editor repeint le tableau tout seul.

Ça se passe mal quand ce message n'arrive jamais. Le guide de dépannage de Supabase décrit le cas : "an underlying Postgres notification queue issue prevents PostgREST from receiving cache reload signals after schema changes". Le journal de PostgREST affiche alors Failed listening for database notifications on the "pgrst" channel, et le tableau reste tel quel, peu importe le nombre de colonnes que vous ajoutez.

Chaque ALTER TABLE sur Supabase envoie le message de rechargement tout seul. Un cache périmé signifie que ce message n'est pas arrivé.

Comment recharger le cache de schéma de Supabase ?

Exécutez une ligne dans le SQL Editor :

NOTIFY pgrst, 'reload schema';

La page de Supabase à ce sujet indique que vous devriez voir "Success. No rows returned" sous Results. C'est sans risque sur une application en service. La documentation de PostgREST dit "Requests will wait until the schema cache reload is done", donc personne qui utilise l'application ne voit d'erreur pendant ce temps.

Si la file de notifications est le problème, le message de rechargement reste bloqué de la même façon que le message automatique. Le guide de dépannage donne une seconde ligne pour ce cas :

select pg_notification_queue_usage();

Selon Supabase, elle pousse PostgREST à mettre à jour son cache et "does not require a database instance or PostgREST service restart". Réessayez ensuite d'enregistrer.

Quand le rechargement ne suffit pas

Redémarrez le projet, et si l'erreur survit à cela, revenez aux causes 1 à 3.

Un redémarrage donne toujours à PostgREST une liste neuve. Le même développeur de PostgREST a écrit sur #42183 qu'un PostgREST redémarré "won't go live if it doesn't load a new schema cache". Restart project se trouve dans Project Settings, puis General, et comment redémarrer un projet Supabase vous guide, y compris pour les quelques minutes où il est hors ligne.

Une erreur qui survit à un redémarrage vient donc d'une liste neuve et exacte : cette table, dans ce projet, n'a pas de colonne de ce nom. #42183 lui-même, ouvert en janvier 2026 par quelqu'un qui avait essayé un rechargement, un redémarrage, puis la suppression et le réajout de la colonne, était toujours ouvert quand nous l'avons lu le 21 octobre 2026. La réponse du développeur y suggérait aussi de passer à PostgREST 14 depuis les réglages Infrastructure du projet.

Rayez supprimer et réajouter de votre liste. alter table … drop column efface chaque valeur stockée dans cette colonne, et la nouvelle colonne du même nom démarre vide. Si vous êtes sur le point de modifier une table dans laquelle de vrais utilisateurs écrivent, gardez une copie de la base d'avant le changement. Reeve Care en fait une selon un calendrier et la conserve chiffrée, sur le stockage propre de Reeve dans l'UE.

Garder vos types générés à jour

Régénérez vos types TypeScript après chaque modification d'une table, et un nom de colonne mal écrit peut apparaître dans l'éditeur avant d'atteindre le site en ligne.

Supabase construit le fichier à partir de vos vraies colonnes, soit depuis le tableau de bord, soit avec une commande tirée de son guide de génération des types :

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

Quand le client est créé avec ces types, createClient<Database>(…), le type Insert de chaque table liste les colonnes que .insert() accepte. Une colonne mal écrite dans un objet écrit en entier dans l'appel est alors signalée comme une erreur avant que vous ne publiiez. La documentation de Lovable dit qu'il "regenerates the TypeScript types your app uses" après une modification de base. Un fichier de types généré avant votre dernière modification de la table liste encore les anciennes colonnes.

Modifier une table en service sans perdre ce qu'elle contient

Gardez une copie d'avant le changement, et sachez tout de suite si l'application répond encore après.

Reeve Care garde une copie de votre base Supabase :

  • une copie chiffrée selon un calendrier, sur le stockage propre de Reeve dans l'UE, en dehors de votre compte Supabase
  • la dernière copie avant votre changement contient les tables et les colonnes telles qu'elles étaient, lignes comprises
  • chaque copie vérifiée avant de compter, en comptant les lignes de chaque table
  • vos fichiers envoyés aussi, dès que vous connectez un identifiant Storage
  • une restauration en un clic, avec une copie de sécurité de l'état actuel prise d'abord
  • tout ce que fait Monitor

Reeve Monitor continue de vérifier depuis l'extérieur :

  • si votre application répond, vérifié chaque minute, pour distinguer facilement un bouton Enregistrer cassé d'une panne
  • le contrôle de sécurité complet toutes les heures, sur trois applications au plus
  • un message quand un résultat change, pour qu'un déploiement qui modifie ce que votre application expose vous parvienne sans que vous alliez voir
  • un rapport mensuel, en langage clair, de ce qu'il a vu

Que faire maintenant

Que faire

  • Lisez les deux noms du message : la clé envoyée par votre application et la table à laquelle elle était destinée.
  • Comparez cette clé à la colonne dans le Table Editor, lettre par lettre. Un nom tapé en SQL sans guillemets est enregistré en minuscules.
  • Cherchez les clés qui ne sont pas des colonnes : des lignes liées imbriquées dans un insert, ou une ligne entière renvoyée avec ses lignes liées. Écrivez chaque table séparément, ou mettez les deux écritures dans une fonction.
  • Vérifiez que la référence dans l'adresse Supabase de votre application correspond au projet que vous avez modifié.
  • Si tout cela correspond, exécutez NOTIFY pgrst, 'reload schema';, puis select pg_notification_queue_usage();, et ne redémarrez le projet que si aucun des deux n'aide.
  • Régénérez vos types TypeScript après chaque modification d'une table.

Commencez par le nom de colonne que cite le message, parce que c'est exactement ce que votre application a envoyé, et le Table Editor montre exactement ce que la table contient. Avant de renommer ou de supprimer quoi que ce soit dans une table que des gens utilisent, gardez une copie de la base d'avant le changement.

FAQ

Que signifie PGRST204 ?

PGRST204 est le code que PostgREST, la partie de Supabase qui répond aux appels de votre application à la base de données, donne à une écriture qui nomme une colonne que la table n'a pas. Il revient avec HTTP 400 et le message "Could not find the 'x' column of 'y' in the schema cache", où x est la clé envoyée par votre application et y la table visée. Les insert, update et upsert le produisent.

J'ai ajouté la colonne et ça échoue encore. Pourquoi ?

Le plus souvent, à cause du nom. Vérifiez que votre application envoie exactement le nom enregistré (une colonne ajoutée en SQL sous le nom phoneNumber, sans guillemets, est enregistrée sous phonenumber), que vous l'avez ajoutée dans le projet que votre application appelle, et que rien d'autre dans la requête n'est une clé que la table ne connaît pas. Supabase recharge le cache de schéma tout seul après chaque ALTER TABLE, donc un cache périmé est la dernière chose à soupçonner.

Comment recharger le cache de schéma de Supabase ?

Exécutez NOTIFY pgrst, 'reload schema'; dans le SQL Editor. Si rien ne change, le guide de dépannage de Supabase donne une seconde commande, select pg_notification_queue_usage();, pour le cas où les messages de rechargement restent bloqués. Les requêtes qui arrivent pendant le rechargement attendent qu'il soit terminé.

Mettre le projet en pause vide-t-il le cache ?

Oui. PostgREST construit un cache de schéma neuf à chaque démarrage, et la reprise d'un projet en pause le redémarre. Restart project, dans Project Settings puis General, fait la même chose sans la pause. Si l'erreur survit à l'un ou à l'autre, le cache avait raison depuis le début : la colonne manque dans cette table, dans ce projet.

Pourquoi les inserts imbriqués échouent-ils avec cette erreur ?

Parce que PostgREST lit chaque clé d'un insert comme une colonne d'une seule table, et une clé qui contient des lignes liées est une colonne qu'il ne trouve pas. Lire des lignes liées en une requête fonctionne, les écrire de cette façon n'a jamais été pris en charge. Insérez d'abord le parent, puis les enfants avec son id, ou faites les deux dans une fonction Postgres appelée avec rpc().

Écrit par

Vlad Tkachenko

Fondateur de Reeve

Je passe mes journées à examiner des applications créées avec Lovable, Bolt, v0, Cursor et Replit, et la courte liste d'erreurs qui y reviennent sans cesse.

En savoir plus sur l'auteur

À lire ensuite

Tous les articles

Sauvegardez votre base Supabase avant la prochaine modification

Reeve en garde une copie chiffrée à intervalles réguliers et la restaure pour vous en cas de problème.

Voir les sauvegardes Supabase

Vérification externe automatisée, pas un audit complet. L'absence de problème n'est pas une garantie de sécurité.