Fundamentos de seguridad
Supabase PGRST202: "Could not find the function in the schema cache"
Supabase "Could not find the function in the schema cache" muestra los nombres de argumentos que envió tu app. Compáralos con la función.

En resumen
- Supabase "Could not find the function in the schema cache" (PGRST202) significa que ninguna función tiene ese nombre y acepta exactamente los nombres de argumentos que envió tu app. Los nombres entre paréntesis en el mensaje son los que envió tu app.
- Revisa primero los nombres de los argumentos: userId enviado a una función que declara user_id, un prefijo p_ que falta, o un valor que estaba en undefined y por eso nunca se envió.
- Supabase actualiza su lista de funciones por sí solo cada vez que se crea, cambia o borra una, así que revisa los nombres y el esquema antes de recargar nada.
Tu builder escribió una función de base de datos para cargar los pedidos de un cliente, y la página que la llama se queda vacía. La consola del navegador muestra esto:
{
"code": "PGRST202",
"details": "Searched for the function public.get_orders with parameter userId or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.",
"hint": "Perhaps you meant to call the function public.get_orders(user_id)",
"message": "Could not find the function public.get_orders(userId) in the schema cache"
}
Supabase "Could not find the function in the schema cache" significa que PostgREST
no tiene ninguna función con ese nombre que acepte exactamente los argumentos que
envió tu app. PostgREST es la parte de Supabase que responde a las llamadas
supabase.rpc() de tu app, y lleva su propia lista de todas las funciones que
puede llamar, llamada schema cache.
Esta es la parte en la que casi todas las respuestas se equivocan: te dicen que
recargues el schema cache. En Supabase esa lista se actualiza sola cada vez que
se crea o se cambia una función. El mensaje ya te ha dicho qué falla. userId,
entre paréntesis, es lo que envió tu app. user_id, en la pista, es lo que espera
la función.
¿Qué significa "Could not find the function in the schema cache"?
Que ninguna función del esquema donde buscó PostgREST tiene ese nombre junto con exactamente esos nombres de argumentos.
Piensa en cada función de la base de datos como un formulario impreso clavado
detrás de un mostrador: su nombre arriba y una casilla rotulada por cada argumento.
Cuando tu app llama a supabase.rpc('get_orders', { userId: id }), entrega una
copia rellenada. PostgREST busca en la pared un formulario con el mismo nombre y
los mismos rótulos. Si ninguno los tiene, la copia se rechaza en el mostrador, y el
mensaje te lee los rótulos que rellenaste.
Cuatro detalles están fijados en el propio código de PostgREST:
- el código es
PGRST202y el estado HTTP 404, el de una página que no existe - los paréntesis listan los nombres de argumentos que envió tu app. Cuando no
envió ninguno, el mensaje dice
without parametersen su lugar public.delante del nombre es el esquema donde buscó PostgREST- la pista, cuando la hay, nombra la función más parecida que PostgREST sí conoce, con los nombres de argumentos de esa función
Compara los paréntesis con la pista antes que nada.
Causa 1: los nombres de los argumentos no coinciden
PostgREST elige una función por su nombre y los nombres de sus argumentos juntos, así que una función llamada con los nombres equivocados es, para PostgREST, una función que no existe.
Las reglas, sacadas de la búsqueda en el planificador de PostgREST:
- cada argumento sin valor por defecto tiene que enviarse, con su nombre exacto
- un argumento con valor por defecto puede faltar
- un nombre que la función no declara la descarta, aunque todos los demás nombres coincidan
Un nombre puede estar mal de tres maneras fáciles de pasar por alto. La primera es
JavaScript encontrándose con SQL: tu app escribe userId y la función declara
user_id.
La segunda es un prefijo. Algunas funciones llaman a sus argumentos p_user_id
para que no se confundan con una columna llamada user_id, y entonces la llamada
también tiene que llevar el p_. En una
discusión de Supabase la
llamada enviaba id, nombre y tres más, la función declaraba p_id, p_nombre
y el resto, y la pista imprimía cada uno de los nombres correctos.
La tercera es la regla que también renombra columnas. Un nombre escrito en SQL sin
comillas dobles se pasa a minúsculas, así que
create function get_orders(userId uuid) guarda el argumento como userid. Un
usuario de FlutterFlow
se topó exactamente con esto: la llamada enviaba userId y la pista respondía
(userid). La versión de columnas de este error
tiene una tabla de lo que crea cada forma de escribirlo.
Para ver los nombres que guardó la base de datos, ejecuta esto en el SQL Editor:
select p.proname, pg_get_function_arguments(p.oid)
from pg_proc p
join pg_namespace n on n.oid = p.pronamespace
where n.nspname = 'public' and p.proname = 'get_orders';
pg_get_function_arguments muestra los argumentos tal como se declararon, con sus
valores por defecto, así que también enseña cuáles puedes omitir.
El arreglo más seguro está en la app, porque no cambia nada en la base de datos.
Pide a tu builder que llame a la función con sus nombres guardados: "get_orders
recibe user_id; pasa { user_id: … } en cada llamada .rpc() a ella".
Renombrar el argumento es más difícil de lo que parece. La
documentación de Postgres
dice de CREATE OR REPLACE FUNCTION: "You cannot change the name already assigned
to any input parameter". Hay que borrar la función y crearla de nuevo, y cualquier
otra pantalla que la llame con el nombre antiguo falla después de la misma manera.
Causa 2: el valor era undefined, así que el nombre nunca se envió
Un argumento cuyo valor es undefined nunca llega a PostgREST, y entonces la
función se busca sin él.
supabase-js envía tus argumentos como JSON, y JSON no tiene undefined. La
referencia de MDN sobre JSON.stringify
dice que esos valores "are either omitted (when found in an object) or changed to
null (when found in an array)". Así que esta llamada, hecha antes de que cargue el
usuario que ha iniciado sesión:
await supabase.rpc('get_orders', { user_id: user?.id })
envía {}, y la respuesta es
Could not find the function public.get_orders without parameters in the schema cache.
Con dos argumentos y uno de ellos en undefined, los paréntesis listan solo el que
llegó.
El nombre en tu código está bien, y por eso leer el código no lo encuentra. El mensaje sí: los paréntesis están vacíos, o les falta un nombre respecto a lo que pasa el código.
El arreglo es hacer la llamada cuando el valor ya exista. Si la función está
escrita para manejar un valor que falta, pasa null a propósito, que JSON sí
conserva.
Un filtro hace lo contrario con el mismo valor. .eq('user_id', user?.id) lo mete
en la petición como la palabra undefined, y una columna uuid responde
invalid input syntax for type uuid.
Causa 3: la función está en otro esquema
supabase.rpc() busca en el esquema public, el que nombra el mensaje, salvo que
le digas otra cosa. Una función creada en otro sitio no está en la lista de
PostgREST:
solo carga funciones
de los esquemas que tu proyecto expone por su API.
Aparece de dos formas:
- la función se creó en un esquema propio, como
privateobilling, y se llama con un.rpc('get_orders')sin más - el esquema va escrito en el nombre, como
.rpc('billing.get_orders'). PostgREST lee toda la cadena como un solo nombre y lo busca enpublic, así que el mensaje dicepublic.billing.get_orders
Una función de otro esquema se llama con .schema('billing').rpc('get_orders', …),
y ese esquema tiene que aparecer en Exposed schemas, en los ajustes de la API del
proyecto, como muestra
la guía de Supabase sobre esquemas propios.
Hasta entonces la respuesta cambia a PGRST106, Invalid schema: billing, con una
pista que enumera los esquemas expuestos.
Exponer un esquema como lo hace esa guía abre cada tabla que contiene. La guía da a
anon y authenticated acceso a cada tabla del nuevo esquema, y desde entonces
solo tus reglas de Row Level Security
deciden lo que un visitante puede leer ahí.
La versión de este error para tablas
tiene la consulta que lista qué tablas de un esquema tienen esas reglas
desactivadas, para ejecutarla antes de exponerlo. Algunas funciones se guardan en un
esquema privado para que nadie pueda llamarlas desde fuera: nuestra
guía sobre la recursión infinita en una policy
pone una ahí precisamente por eso. Una llamada a una función así desde el navegador
recibe PGRST202 a propósito, y su esquema debe seguir sin exponerse.
Causa 4: el nombre de la propia función, o ninguna función
PostgREST busca el nombre de forma exacta, letra por letra y con mayúsculas y minúsculas.
- una errata, o
getOrdersen la llamada frente aget_ordersen la base de datos - una función creada como
getOrderssin comillas dobles, que se guarda comogetorderspor la misma regla que sus argumentos - un procedimiento. PostgREST solo lista funciones, así que
create procedurecrea algo a lo que.rpc()no llega, y lo mismo pasa con una función quereturns trigger, que se ejecuta desde un trigger y nunca desde tu app
La pista también ayuda aquí. Cuando no existe ninguna función con ese nombre, PostgREST sugiere el nombre más cercano que tiene, si alguno se parece lo suficiente.
O la función nunca se creó en la base de datos a la que llama tu app. Si construyes con Lovable, repasa el chat buscando SQL que te pidieran 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 apruebas, la función solo existe en el código que la llama. Si alguien la creó a mano, comprueba que fue al proyecto con el que habla tu app: una rama es un proyecto aparte con su propia base de datos.
Causa 5: el caché de verdad está desactualizado
En Supabase este es el caso raro. Cada base de datos de Supabase tiene un event
trigger, pgrst_ddl_watch, que envía NOTIFY pgrst, 'reload schema' después de
cada CREATE FUNCTION y ALTER FUNCTION, y un segundo, pgrst_drop_watch, lo
envía cuando se borra una función. Los dos están en
la configuración de base de datos de Supabase.
Cuando ese mensaje no llega, recarga la lista a mano en el SQL Editor:
NOTIFY pgrst, 'reload schema';
Si nada cambia, la versión de columnas de este error cubre los dos pasos siguientes, la cola de notificaciones atascada y el reinicio del proyecto. Los dos funcionan igual para las funciones.
Cuando dos funciones comparten un nombre
PostgREST no adivina entre ellas. Cuando dos funciones tienen el mismo nombre y las
dos aceptan los argumentos que envió tu app, la llamada falla con PGRST203 y
HTTP 300:
Could not choose the best candidate function between: public.get_orders(user_id => uuid), public.get_orders(user_id => text)
Arreglar un PGRST202 es una forma de llegar aquí. Tu builder cambia el tipo de un
argumento con CREATE OR REPLACE FUNCTION, y Postgres no reemplaza nada. Su
documentación
dice que los tipos de los argumentos no se pueden cambiar así: "if you tried, you
would actually be creating a new, distinct function". Ahora existen las dos
versiones una al lado de la otra.
La pista de PostgREST para este caso dice "Try renaming the parameters or the function itself in the database so function overloading can be resolved". El arreglo más sencillo es borrar la versión que no quieres, indicando el tipo de su argumento para que Postgres sepa a cuál te refieres:
drop function public.get_orders(text);
Comprueba qué versión llama tu app antes de borrar cualquiera de las dos.
Cómo llamar bien a una función de Supabase
Pasa un objeto cuyas claves sean los nombres de los argumentos de la función,
escritos como los guardó la base de datos. En
la guía de Supabase sobre funciones de base de datos,
una función declarada como add_planet(name text) se llama con
supabase.rpc('add_planet', { name: 'Jakku' }).
En cuanto tu app puede llamar a la función, cualquier otra persona también puede.
La misma guía lo dice claro: "By default, any role can run a database function."
Eso incluye anon, el rol detrás de cada petición de un visitante que no ha
iniciado sesión. Una función que tu app llama desde el navegador la puede llamar
cualquiera que tenga tu clave pública, con los argumentos que quiera.
Para una función que solo deben ejecutar usuarios con sesión iniciada, sigue la
guía: quita execute a public y a anon, y devuélveselo a authenticated.
revoke execute on function public.get_orders from public, anon;
grant execute on function public.get_orders to authenticated;
Tus tablas son la otra mitad de lo que alcanza esa clave pública. Nuestro escaneo gratuito comprueba desde fuera si entregan filas a cualquiera que la tenga, y no necesita cuenta.
Cambiar una función de la que depende tu app
Guarda una copia de la base de datos de antes del cambio, y entérate cuando cambie algo que tu app expone.
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 copia de antes de tu cambio tiene tus funciones tal como estaban definidas entonces, junto a tus tablas y filas
- 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
- 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 el mensaje: los nombres entre paréntesis son lo que envió tu app, y la pista nombra lo que tiene la base de datos.
- Compara los nombres de los argumentos letra por letra, prefijo
p_incluido. Un nombre escrito en SQL sin comillas se guarda en minúsculas. - Si los paréntesis están vacíos o les falta un nombre, busca un valor que estaba en
undefinedcuando se hizo la llamada. - Revisa el esquema del mensaje. Una función de otro esquema necesita
.schema('nombre').rpc(…)y un esquema expuesto, y exponer uno hace accesibles también sus tablas. - Si dos funciones comparten el nombre, borra la versión que tu app no llama.
- Ejecuta
NOTIFY pgrst, 'reload schema';solo cuando los nombres ya coincidan. - Quita
executeaanonen cualquier función que un visitante sin sesión no deba ejecutar nunca.
Empieza por los paréntesis, porque contienen exactamente lo que envió tu app. Antes de borrar o reemplazar una función de la que depende tu app, guarda una copia de la base de datos de antes del cambio.
Preguntas frecuentes
¿Qué significa PGRST202?
PGRST202 es el código que devuelve PostgREST, la parte de Supabase que responde a las llamadas supabase.rpc(), cuando ninguna función coincide con una llamada. Llega con HTTP 404 y el mensaje "Could not find the function public.name(arguments) in the schema cache". Los nombres entre paréntesis son los argumentos que envió tu app, y public es el esquema donde buscó.
La función existe. ¿Por qué mi llamada RPC dice que falta?
PostgREST reconoce una función por su nombre y los nombres de sus argumentos juntos. Envía cada argumento que no tenga valor por defecto, con exactamente el nombre que guardó la base de datos, y nada que la función no declare. Un nombre escrito en SQL sin comillas dobles se guarda en minúsculas, así que userId en un create function se convierte en userid.
¿Por qué el error dice "without parameters"?
Tu app no envió ningún argumento. Con supabase-js la razón habitual es un valor que estaba en undefined cuando se hizo la llamada: los argumentos viajan como JSON, que deja fuera los valores undefined, así que { user_id: undefined } llega como {}. Haz la llamada cuando el valor ya exista, o pasa null si la función está escrita para manejar un valor que falta.
¿La función tiene que estar en el esquema public?
supabase.rpc() busca en public salvo que elijas otro esquema con .schema('nombre'), y ese esquema tiene que aparecer en Exposed schemas, en los ajustes de la API del proyecto. Exponer un esquema hace accesible por la API lo que hay en él, así que conviene decidirlo a propósito.
¿Cómo recargo el schema cache de Supabase?
Ejecuta NOTIFY pgrst, 'reload schema'; en el SQL Editor. Supabase ya envía ese mensaje por sí solo cada vez que se crea, cambia o borra una función, así que recargar solo ayuda cuando el mensaje no llegó. Revisa primero los nombres.