Saltar al contenido

Fundamentos de seguridad

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

Supabase "Could not find the column in the schema cache" suele ser un nombre que no coincide. Cuatro comprobaciones y la línea que recarga.

Vlad Tkachenko12 min de lectura
Un directorio de vestíbulo con un hueco vacío en su lista de salas y, delante, una hoja de entrega con una línea resaltada en ámbar.

En resumen

  • Supabase "Could not find the column in the schema cache" (PGRST204) significa que tu app intentó guardar un campo que la tabla no tiene en su lista. PostgREST compara cada clave de un insert o un update con su propia copia de tu esquema, letra por letra.
  • La causa habitual es el nombre: phoneNumber enviado a una columna llamada phone_number, o una clave que nunca fue una columna, como filas relacionadas anidadas dentro de un insert.
  • Supabase recarga esa copia por sí solo cada vez que cambia una tabla. Cuando de verdad está desactualizada, NOTIFY pgrst, 'reload schema'; en el SQL Editor la recarga mientras la app sigue funcionando.

Le pediste a tu builder que añadiera un número de teléfono al formulario de perfil. La columna está en el Table Editor. Pulsas Guardar, no pasa nada, y la consola del navegador muestra esto:

{
  "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 tu app intentó guardar un campo que la tabla, tal como la ve PostgREST, no tiene. PostgREST es la parte de Supabase que convierte las llamadas supabase.from(...) de tu app en consultas a la base de datos. Guarda su propia lista de cada tabla y cada columna, llamada schema cache, para no tener que preguntarle a la base de datos por su forma en cada petición.

Esta es la parte que casi todas las respuestas cuentan mal: te dicen que recargues la schema cache. En Supabase la caché se recarga sola cada vez que cambia una tabla. Así que si acabas de añadir una columna y sigues viendo este error, la caché es la razón menos probable. Lo habitual es que sea el nombre.

¿Qué significa "Could not find the column in the schema cache"?

Una clave de una escritura que envió tu app no tiene columna correspondiente en esa tabla.

Piensa en la schema cache como el directorio del vestíbulo de un edificio: una línea para cada oficina, que es una tabla, y debajo cada sala, que es una columna. Cada petición de tu app es una hoja de entrega. PostgREST lee cada línea de la hoja contra el directorio, y una sala que el directorio no tiene se rechaza en recepción, antes de que nada llegue a tus datos.

El mensaje nombra los dos lados de esa comparación. En Could not find the 'phoneNumber' column of 'profiles' in the schema cache, phoneNumber es lo que envió tu app y profiles la tabla a la que iba. Otras tres cosas están fijadas en el propio código de PostgREST:

  • el código es PGRST204 y el estado HTTP 400, que significa que la petición misma estaba mal
  • solo sale de escrituras: .insert(), .update() y .upsert(). Una lectura que nombra una columna inexistente falla con otro error, escrito por Postgres
  • no trae ninguna pista. Para una tabla que falta, PostgREST sugiere el nombre más parecido que conoce; para una columna que falta, hint es null y la comparación te toca a ti

Cada clave del objeto que pasas a una de esas tres llamadas tiene que ser una columna de esa tabla, escrita como la escribe la tabla.

PostgREST compara cada clave que envía tu app con las columnas de su lista, letra por letra. phoneNumber y phone_number son dos nombres distintos.

Causa 1: el nombre no coincide, letra por letra

La comprobación es exacta. Para PostgREST, phoneNumber, phone_number y phonenumber son tres columnas distintas, y tu tabla tiene como mucho una.

Hay dos formas habituales de llegar aquí. La primera son dos costumbres de nombres que se cruzan: JavaScript suele escribir un campo como phoneNumber, y una columna de Postgres suele llamarse phone_number. El código que escribió tu builder y el SQL que ejecutó pueden estar bien cada uno por su lado y aun así no coincidir.

La segunda es la trampa de las comillas. En SQL, un nombre escrito sin comillas dobles se pasa a minúsculas. La documentación de Postgres lo dice claramente: "Quoting an identifier also makes it case-sensitive, whereas unquoted names are always folded to lower case."

Lo que ejecutaste en el SQL EditorLa columna que creóLo que tu app tiene que enviar
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

La fila del medio es la que atrapa a la gente. El SQL parece haber creado phoneNumber, el código envía phoneNumber, y la columna es phonenumber.

Para ver los nombres reales, abre la tabla en el Table Editor y lee los encabezados de las columnas, o ejecuta esto en el SQL Editor:

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

Después mira qué envió tu app. En las herramientas de desarrollo del navegador, abre la pestaña Red (Network), haz clic en la petición fallida a /rest/v1/profiles y lee su payload.

La solución más segura es cambiar la app, porque no toca nada de tus datos. Pídele a tu builder que use el nombre exacto de la columna en todos los sitios donde se guarda ese campo: "la columna de profiles es phone_number; usa ese nombre en cada insert y cada update". Renombrar la columna en su lugar es un cambio en una tabla en uso, y cualquier otra pantalla que siga usando el nombre antiguo se rompe la primera vez que se ejecuta.

Si construyes con Lovable, repasa el chat buscando un cambio de base de datos que te pidió aprobar. La documentación de Supabase de Lovable dice que "writes the SQL, shows it to you, and asks for your approval in the project chat before running it". Hasta que lo apruebes, la columna no existe, espere lo que espere el código.

Causa 2: una clave que nunca fue una columna

La petición lleva algo que la tabla nunca debía guardar: filas relacionadas anidadas dentro del insert, o un campo que el formulario usa para sí mismo.

El insert anidado es el que la gente espera que funcione:

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

Eso falla con Could not find the 'order_items' column of 'orders' in the schema cache. Leer la misma forma funciona: .select('*, order_items(*)') devuelve un pedido con sus artículos, así que parece que lo contrario también debería. PostgREST lee cada clave de una escritura como una columna de la única tabla a la que va la petición. Un usuario de Supabase lo reportó como un bug, y la respuesta, antes de que el issue se cerrara el 18 de octubre de 2026, fue que los inserts anidados son una función que falta. La petición lleva abierta en el tracker de PostgREST desde febrero de 2017.

Otras dos formas del mismo problema:

  • una fila leída junto con sus filas relacionadas, editada en un formulario y devuelta entera en .update(), de modo que el order_items o el profiles adjunto va con ella
  • el estado completo de un formulario pasado tal cual a .insert(), con campos que necesita el formulario y no la tabla, como confirmPassword

La solución es escribir cada tabla por separado, primero el padre. Inserta el pedido y pide su id de vuelta con .select('id').single(), y después inserta los artículos con ese order_id. Son dos peticiones, así que si falla la segunda el pedido queda guardado sin sus artículos. Cuando eso no debe pasar nunca, pon los dos inserts en una función de Postgres y llámala con .rpc(), que la respuesta en ese issue llama el patrón estándar. Así se guarda todo o nada. Si esa llamada responde con PGRST202, la versión de este error para funciones explica por qué.

Una hoja anidada se lee como una columna más de la primera tabla. Enviadas por separado, primero el padre, cada una encuentra su propio directorio.

Causa 3: el cambio fue a otro proyecto

La columna existe, en una base de datos con la que tu app nunca habla. Una cuenta de Supabase puede tener varios proyectos, y una rama es un proyecto aparte con su propia base de datos. Una columna añadida en el SQL Editor del equivocado cambia el directorio de otro edificio.

En el issue #42183 de Supabase, un reporte de que la caché "does not update" se intentara lo que se intentara, uno de los desarrolladores de PostgREST respondió que el reporte mencionaba dos referencias de proyecto distintas, y pidió confirmar que el cambio se había hecho en el proyecto al que iban las peticiones.

La comprobación usa dos direcciones. Tu app llama a https://<reference>.supabase.co, que puedes ver en cualquier petición de la pestaña Red. El panel muestra el proyecto en el que estás en supabase.com/dashboard/project/<reference>. Las dos referencias tienen que ser la misma cadena. Cuál es mi proyecto explica cómo encontrarlo cuando otra persona configuró Supabase.

Causa 4: la caché de verdad está desactualizada

Es el caso raro en Supabase, porque Supabase recarga la caché por ti. Cada base de datos de Supabase se configura con un disparador de eventos, pgrst_ddl_watch, que se ejecuta después de cada cambio en la forma de la base de datos, ALTER TABLE incluido, y envía NOTIFY pgrst, 'reload schema': el mensaje que le dice a PostgREST que reconstruya su lista. Puedes leerlo en la configuración de base de datos de Supabase. Añadir una columna en el Table Editor o en el SQL Editor vuelve a rotular el directorio por sí solo.

Sale mal cuando ese mensaje no llega nunca. La guía de solución de problemas de Supabase describe el caso: "an underlying Postgres notification queue issue prevents PostgREST from receiving cache reload signals after schema changes". El registro de PostgREST muestra entonces Failed listening for database notifications on the "pgrst" channel, y el directorio se queda como estaba por muchas columnas que añadas.

Cada ALTER TABLE en Supabase envía el mensaje de recarga por sí solo. Una caché desactualizada significa que ese mensaje no llegó.

¿Cómo recargo la schema cache de Supabase?

Ejecuta una línea en el SQL Editor:

NOTIFY pgrst, 'reload schema';

La página de Supabase sobre esto dice que deberías ver "Success. No rows returned" bajo Results. Es seguro en una app en uso. La documentación de PostgREST dice "Requests will wait until the schema cache reload is done", así que nadie que esté usando la app ve un error mientras tanto.

Si el problema es la cola de notificaciones, el mensaje de recarga se atasca igual que el automático. La guía de solución de problemas da una segunda línea para ese caso:

select pg_notification_queue_usage();

Según Supabase, hace que PostgREST actualice su caché y "does not require a database instance or PostgREST service restart". Después vuelve a intentar guardar.

Cuando recargar no sirve

Reinicia el proyecto, y si el error sobrevive a eso, vuelve a las causas 1 a 3.

Un reinicio siempre le da a PostgREST una lista nueva. El mismo desarrollador de PostgREST escribió en #42183 que un PostgREST reiniciado "won't go live if it doesn't load a new schema cache". Restart project está en Project Settings, luego General, y cómo reiniciar un proyecto de Supabase te guía, incluidos los pocos minutos que pasa sin conexión.

Así que un error que sobrevive a un reinicio viene de una lista nueva y exacta: esa tabla, en ese proyecto, no tiene una columna con ese nombre. El propio #42183, abierto en enero de 2026 por alguien que había probado una recarga, un reinicio y borrar y volver a añadir la columna, seguía abierto cuando lo leímos el 21 de octubre de 2026. La respuesta del desarrollador allí también sugería actualizar a PostgREST 14 desde los ajustes de Infrastructure del proyecto.

Tacha borrar y volver a añadir de tu lista. alter table … drop column borra cada valor guardado en esa columna, y la nueva columna con el mismo nombre empieza vacía. Si estás a punto de cambiar una tabla en la que escriben usuarios reales, guarda una copia de la base de datos de antes del cambio. Reeve Care hace una según un calendario y la guarda cifrada, en el almacenamiento propio de Reeve en la UE.

Mantener tus tipos generados al día

Vuelve a generar tus tipos de TypeScript después de cada cambio en una tabla, y un nombre de columna mal escrito puede aparecer en el editor antes de llegar al sitio en vivo.

Supabase construye el archivo a partir de tus columnas reales, desde el panel o con un comando de su guía para generar tipos:

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

Cuando el cliente se crea con esos tipos, createClient<Database>(…), el tipo Insert de cada tabla lista las columnas que acepta .insert(). Una columna mal escrita en un objeto escrito completo dentro de la llamada se marca entonces como error antes de publicar. La documentación de Lovable dice que "regenerates the TypeScript types your app uses" después de un cambio en la base de datos. Un archivo de tipos generado antes de tu último cambio en la tabla todavía lista las columnas antiguas.

Cambiar una tabla en uso sin perder lo que contiene

Guarda una copia de antes del cambio y sabe al momento si la app sigue respondiendo después.

Reeve Care guarda una copia de tu base de datos de Supabase:

  • una copia cifrada según un calendario, en el almacenamiento propio de Reeve en la UE, fuera de tu cuenta de Supabase
  • la última copia antes de tu cambio tiene las tablas y columnas como estaban, filas incluidas
  • cada copia verificada antes de contar, contando las filas de cada tabla
  • también tus archivos subidos, en cuanto conectas una credencial de Storage
  • restauración con un clic, con una copia de seguridad del estado actual tomada primero
  • todo lo que hace Monitor

Reeve Monitor sigue comprobando desde fuera:

  • si tu app está en línea, comprobado cada minuto, para distinguir fácilmente un botón Guardar roto de una caída
  • la comprobación de seguridad completa cada hora, en hasta tres apps
  • un mensaje cuando un resultado cambia, para que un deploy que cambia lo que tu app expone te llegue sin que tengas que ir a mirar
  • un informe mensual, en lenguaje claro, de lo que vio

Qué hacer ahora

Qué hacer

  • Lee los dos nombres del mensaje: la clave que envió tu app y la tabla a la que fue.
  • Compara esa clave con la columna en el Table Editor, letra por letra. Un nombre escrito en SQL sin comillas se guarda en minúsculas.
  • Busca claves que no son columnas: filas relacionadas anidadas en un insert, o una fila entera devuelta con sus filas relacionadas. Escribe cada tabla por separado, o pon las dos escrituras en una función.
  • Comprueba que la referencia en la dirección de Supabase de tu app coincide con el proyecto que cambiaste.
  • Si todo eso coincide, ejecuta NOTIFY pgrst, 'reload schema';, después select pg_notification_queue_usage();, y reinicia el proyecto solo si ninguno de los dos ayuda.
  • Vuelve a generar tus tipos de TypeScript después de cada cambio en una tabla.

Empieza por el nombre de columna que cita el mensaje, porque es exactamente lo que envió tu app, y el Table Editor muestra exactamente lo que tiene la tabla. Antes de renombrar o borrar algo en una tabla que la gente usa, guarda una copia de la base de datos de antes del cambio.

Preguntas frecuentes

¿Qué significa PGRST204?

PGRST204 es el código que PostgREST, la parte de Supabase que responde a las llamadas de tu app a la base de datos, le da a una escritura que nombra una columna que la tabla no tiene. Llega con HTTP 400 y el mensaje "Could not find the 'x' column of 'y' in the schema cache", donde x es la clave que envió tu app e y la tabla a la que la envió. Lo producen los insert, update y upsert.

Añadí la columna y sigue fallando. ¿Por qué?

Lo habitual es el nombre. Comprueba que tu app envía exactamente el nombre guardado (una columna añadida en SQL como phoneNumber, sin comillas, queda guardada como phonenumber), que la añadiste en el proyecto al que llama tu app y que nada más en la petición es una clave que la tabla no tiene. Supabase recarga la schema cache por sí solo después de cada ALTER TABLE, así que una caché desactualizada es lo último de lo que sospechar.

¿Cómo recargo la schema cache de Supabase?

Ejecuta NOTIFY pgrst, 'reload schema'; en el SQL Editor. Si eso no cambia nada, la guía de solución de problemas de Supabase da un segundo comando, select pg_notification_queue_usage();, para cuando los mensajes de recarga se quedan atascados. Las peticiones que llegan durante la recarga esperan a que termine.

¿Pausar el proyecto la vacía?

Sí. PostgREST construye una schema cache nueva cada vez que arranca, y reanudar un proyecto pausado lo vuelve a arrancar. Restart project, en Project Settings y luego General, hace lo mismo sin la pausa. Si el error sobrevive a cualquiera de los dos, la caché tenía razón desde el principio: a esa tabla, en ese proyecto, le falta la columna.

¿Por qué los inserts anidados fallan con este error?

Porque PostgREST lee cada clave de un insert como una columna de una sola tabla, y una clave que contiene filas relacionadas es una columna que no encuentra. Leer filas relacionadas en una petición funciona; escribirlas así nunca ha sido posible. Inserta primero el padre y luego los hijos con su id, o haz las dos cosas dentro de una función de Postgres llamada con rpc().

Escrito por

Vlad Tkachenko

Fundador de Reeve

Me paso el día mirando apps creadas con Lovable, Bolt, v0, Cursor y Replit, y la corta lista de errores que aparecen en ellas una y otra vez.

Más sobre el autor

Sigue leyendo

Todos los artículos

Haz una copia de tu base de datos de Supabase antes del próximo cambio

Reeve la guarda cifrada con regularidad y la restaura por ti si algo falla.

Ver copias de Supabase

Comprobación externa automatizada, no una auditoría completa. La ausencia de hallazgos no es garantía de seguridad.