Supabase: "new row violates row-level security policy" (42501)
El error “new row violates row-level security policy” (código 42501) significa que tu app intentó crear o modificar una fila en una tabla de Supabase y ninguna política RLS lo permite para ese usuario. Casi nunca es un bug de Supabase: es una política que falta, una política demasiado estricta o tu app enviando datos que no cumplen la regla (por ejemplo, un user_id vacío). La solución no es desactivar RLS.
El mensaje completo se ve así:
{
"code": "42501",
"message": "new row violates row-level security policy for table \"pedidos\""
}
En español: “la fila nueva viola la política de seguridad a nivel de fila de la tabla pedidos”.
Las 6 causas más comunes (y cómo arreglar cada una)
1. No existe una política de INSERT
Tienes RLS activo y políticas para leer (select), pero ninguna para crear (insert). Revisa en Supabase: Authentication → Policies (o en Table Editor, la tabla) y crea una:
create policy "Usuarios crean sus propios pedidos"
on public.pedidos for insert
to authenticated
with check ( (select auth.uid()) = user_id );
2. Tu app no está enviando el user_id (o envía otro)
La política dice “solo si user_id es el usuario actual”, pero tu código manda user_id vacío o no lo manda. Dos soluciones:
Enviarlo desde la app:
const { data: { user } } = await supabase.auth.getUser();
await supabase.from('pedidos').insert({
producto: 'Playera',
user_id: user.id,
});
O que la base de datos lo ponga sola (más seguro, porque no depende del frontend):
alter table public.pedidos
alter column user_id set default auth.uid();
3. El usuario no ha iniciado sesión (es “anon”)
Si la política es to authenticated y la petición sale sin sesión, se bloquea. Pasa cuando:
- El usuario no confirmó su correo todavía.
- La sesión expiró.
- Haces el
insertantes de que termine de cargar la sesión.
Revisa en la consola del navegador si supabase.auth.getSession() regresa una sesión. Si un formulario público (contacto, registro a evento) debe guardar sin login, necesitas una política explícita para anon, y pensar bien qué permites:
create policy "Formulario de contacto público"
on public.mensajes_contacto for insert
to anon
with check ( true );
Sin políticas de select para anon, nadie podrá leer esos mensajes desde la app, que es lo correcto.
4. Es un UPDATE o UPSERT, no un INSERT
El mismo error sale al editar. Para update, Postgres revisa using (¿puedes tocar esta fila?) y with check (¿la fila resultante es válida?). Un upsert además necesita políticas de insert y de update, y normalmente de select.
create policy "Usuarios editan sus pedidos"
on public.pedidos for update
to authenticated
using ( (select auth.uid()) = user_id )
with check ( (select auth.uid()) = user_id );
5. Haces insert y luego lees el resultado (.select())
En supabase-js, esto inserta y lee la fila creada:
await supabase.from('pedidos').insert({ ... }).select();
Si tienes política de insert pero no de select para esa fila, la lectura falla. Agrega una política de select adecuada o quita .select() si no necesitas la respuesta.
6. El error es al subir un archivo (Storage)
Supabase Storage guarda los archivos en la tabla storage.objects, también con RLS. Necesitas una política para el bucket. Ejemplo: cada usuario sube a una carpeta con su ID:
create policy "Usuarios suben a su carpeta"
on storage.objects for insert
to authenticated
with check (
bucket_id = 'documentos'
and (storage.foldername(name))[1] = (select auth.uid())::text
);
Y en la app sube a la ruta ${user.id}/archivo.pdf.
7. Un trigger inserta en otra tabla
Si el mensaje menciona una tabla distinta a la que tu app está usando (por ejemplo, insertas en pedidos y el error habla de historial), probablemente un trigger (una acción automática de la base de datos) está escribiendo en esa otra tabla con los permisos del usuario. Un caso muy común en apps hechas con IA: el trigger que crea la fila en profiles cuando alguien se registra. Ese trigger corre durante el registro, cuando el usuario todavía no tiene sesión. La solución habitual es que la función del trigger se declare con security definer (corre con los permisos de quien la creó), con set search_path = '' y nombres de tabla completos (public.profiles), para que no se pueda abusar de ella.
Cómo probar tu política sin usar la app
En el SQL Editor puedes simular a un usuario concreto dentro de una transacción que luego deshaces. Así compruebas la política sin ensuciar datos:
begin;
set local role authenticated;
set local request.jwt.claims = '{"sub":"UUID-DEL-USUARIO","role":"authenticated"}';
insert into public.pedidos (producto, user_id)
values ('Playera', 'UUID-DEL-USUARIO');
rollback;
Cambia UUID-DEL-USUARIO por un ID real de Authentication → Users. Si aquí sale el error 42501, el problema es la política; si no sale, el problema está en lo que manda tu app (sesión o datos). Prueba también con otro user_id distinto al del usuario: la política debe rechazarlo.
Supabase error 42501: otros casos
El código 42501 en Postgres significa en general “privilegio insuficiente”. Además del mensaje de RLS, puedes ver permission denied for table pedidos. Eso no es RLS: es que el rol (anon o authenticated) no tiene permiso básico sobre la tabla. Pasa con tablas creadas por SQL en ciertos casos. Se resuelve otorgando el permiso, y RLS sigue controlando qué filas:
grant select, insert, update on public.pedidos to authenticated;
Otorga solo lo que de verdad necesita cada rol.
Lo que NO debes hacer
- Desactivar RLS (
disable row level security). Funciona, pero deja tu tabla abierta. Muchas IAs lo sugieren; no lo aceptes. Ve Supabase RLS disabled in public. - Crear
with check (true)paraauthenticateden tablas de usuarios: cualquier usuario podría crear filas a nombre de otro. - Usar la llave service_role en el frontend “para que ya funcione”: se salta todo RLS. Ve service_role key expuesta.
Cómo diagnosticarlo rápido
- ¿Qué operación es? (insert, update, upsert, upload).
- ¿Hay política para esa operación y ese rol? Revisa en el panel.
- ¿El usuario tiene sesión?
- ¿Qué datos se envían? Revisa en
F12→ Network la petición fallida y su cuerpo. - ¿La condición de la política se cumple con esos datos?
Más plantillas en Supabase RLS: ejemplos de políticas y la guía oficial de RLS en Supabase.
En resumen
“New row violates row-level security policy” significa que falta o no se cumple la política de esa operación; se arregla con la política correcta o enviando los datos correctos, nunca apagando RLS. Si tu app hecha con IA sigue tirando este error, o la IA “lo arregló” y no sabes si dejó la tabla abierta, en Black Engine lo revisamos contigo por WhatsApp.
Preguntas frecuentes
¿Qué significa 'new row violates row-level security policy'?
Que intentaste crear o modificar una fila y ninguna política RLS de esa tabla lo permite para el usuario actual. Supabase lo bloquea con el código 42501.
¿Lo arreglo desactivando RLS?
No. Desactivar RLS deja la tabla abierta a cualquiera. La solución es corregir la política o los datos que envía tu app.
¿Por qué funciona en el SQL Editor y no en mi app?
El SQL Editor corre con permisos de administrador y se salta RLS. Tu app corre como usuario anon o authenticated, que sí está sujeto a las políticas.
¿Por qué el error sale al subir un archivo?
Supabase Storage también usa RLS en la tabla storage.objects. Necesitas una política de insert para el bucket al que estás subiendo.