>_ Black Engine

Supabase RLS policy: 8 ejemplos de políticas listos para usar

Por Black Engine · 6 de noviembre de 2026

Aquí tienes ejemplos de políticas RLS para Supabase listos para adaptar, explicados en español. Cada policy es una regla que permite una acción (select, insert, update, delete) a un rol (anon, authenticated) bajo una condición. Cambia los nombres de tabla y columnas por los tuyos y prueba con dos usuarios antes de publicar.

Si todavía no tienes claro qué es RLS, lee primero RLS en Supabase: qué es. La referencia oficial es la guía de Row Level Security de Supabase.

Antes de copiar: cómo se lee una política

create policy "nombre descriptivo"
on public.tabla
for select              -- select | insert | update | delete | all
to authenticated        -- anon | authenticated | otro rol
using ( condición )         -- sobre filas existentes
with check ( condición );   -- sobre filas nuevas o editadas
  • select y delete solo usan using.
  • insert solo usa with check.
  • update usa los dos.
  • Escribe (select auth.uid()) en vez de auth.uid(): Supabase lo recomienda porque se calcula una vez por consulta.

Supabase RLS policy: ejemplos

1. Cada usuario ve y maneja solo sus filas

Tabla notas con columna user_id uuid:

alter table public.notas enable row level security;

create policy "Ver mis notas" on public.notas
for select to authenticated
using ( (select auth.uid()) = user_id );

create policy "Crear mis notas" on public.notas
for insert to authenticated
with check ( (select auth.uid()) = user_id );

create policy "Editar mis notas" on public.notas
for update to authenticated
using ( (select auth.uid()) = user_id )
with check ( (select auth.uid()) = user_id );

create policy "Borrar mis notas" on public.notas
for delete to authenticated
using ( (select auth.uid()) = user_id );

El with check en el update impide que alguien “regale” su nota a otro usuario cambiando el user_id.

2. Catálogo público de solo lectura

alter table public.productos enable row level security;

create policy "Productos visibles para todos" on public.productos
for select to anon, authenticated
using ( activo = true );

Sin políticas de insert/update/delete, nadie puede modificar el catálogo desde la app. Lo administras desde el panel o con tu backend.

3. Perfil: todos lo ven, solo el dueño lo edita

Tabla perfiles donde id es el mismo que el del usuario:

create policy "Perfiles públicos" on public.perfiles
for select to authenticated
using ( true );

create policy "Editar mi perfil" on public.perfiles
for update to authenticated
using ( (select auth.uid()) = id )
with check ( (select auth.uid()) = id );

Ojo: si perfiles tiene teléfono o dirección, using (true) los muestra a todos los usuarios. Separa los datos privados en otra tabla.

4. Administradores (forma segura)

Nunca uses un campo es_admin dentro de perfiles si el usuario puede editar su perfil: se haría admin él solo. Crea una tabla aparte:

create table public.admins (
  user_id uuid primary key references auth.users(id)
);
alter table public.admins enable row level security;
-- sin políticas: nadie la lee ni la modifica desde la app

create or replace function public.es_admin()
returns boolean
language sql
security definer
set search_path = ''
stable
as $$
  select exists (
    select 1 from public.admins where user_id = (select auth.uid())
  );
$$;

create policy "Admins ven todos los pedidos" on public.pedidos
for select to authenticated
using ( (select public.es_admin()) );

La función usa security definer para poder leer admins aunque el usuario no tenga acceso a esa tabla, y search_path = '' como recomienda Supabase. Esta política se suma a la de “ver mis pedidos”: el usuario normal ve lo suyo y el admin ve todo.

5. Equipos u organizaciones

Tabla miembros (org_id, user_id) y tabla proyectos (org_id, ...):

create policy "Miembros ven proyectos de su organización" on public.proyectos
for select to authenticated
using (
  org_id in (
    select org_id from public.miembros
    where user_id = (select auth.uid())
  )
);

Pon índices en miembros(user_id) y proyectos(org_id) para que no se vuelva lenta.

6. Formulario público (insertar sin login, nadie lee)

create policy "Cualquiera envía un mensaje" on public.contacto
for insert to anon, authenticated
with check ( char_length(mensaje) < 2000 );

Sin política de select, los mensajes solo se leen desde el panel o tu backend. Agrega protección contra spam (captcha o límite de envíos) aparte.

7. Contenido publicado vs borradores

create policy "Ver publicados o mis borradores" on public.articulos
for select to anon, authenticated
using ( publicado = true or (select auth.uid()) = autor_id );

8. Archivos en Storage por carpeta de usuario

create policy "Leer mis archivos" on storage.objects
for select to authenticated
using (
  bucket_id = 'documentos'
  and (storage.foldername(name))[1] = (select auth.uid())::text
);

Haz una igual para insert (con with check) y, si aplica, para delete.

Supabase RLS policy: errores que vemos seguido

Error Por qué es un problema
using (true) en tablas con datos personales Todos ven todo
with check (true) en insert de usuarios Cualquiera crea filas a nombre de otro
Política for all copiada sin pensar Da leer, crear, editar y borrar de golpe
Rol de admin en un campo editable Cualquiera se vuelve admin
Olvidar with check en update Se puede cambiar el dueño de una fila
Vistas sin security_invoker = true La vista se salta RLS

Recuerda: las políticas permisivas se suman (OR). Si tienes una estricta y otra abierta en la misma tabla, gana la abierta.

Revisa qué políticas tienes hoy

Si tu app la generó Lovable, Bolt u otra herramienta, probablemente ya hay políticas que nadie leyó. Antes de agregar nuevas, lístalas en el SQL Editor de Supabase:

select tablename, policyname, cmd, roles, qual, with_check
from pg_policies
where schemaname = 'public'
order by tablename;
  • cmd es la acción (SELECT, INSERT, ALL…).
  • qual es la condición del using y with_check la del with check.
  • Busca true en esas columnas: es la señal de una política abierta.

Y para ver qué tablas ni siquiera tienen RLS activado:

select tablename
from pg_tables
where schemaname = 'public' and rowsecurity = false;

Si te sale alguna tabla en esta lista, cualquiera con la llave pública puede leerla. Es el problema detrás de la alerta “RLS disabled in public”.

Cuando necesitas una regla que siempre se cumpla: políticas restrictivas

Como las políticas normales se suman, a veces conviene una regla que se tenga que cumplir siempre, sin importar las demás. Para eso existen las políticas as restrictive, que se combinan con AND:

create policy "Solo cuentas no suspendidas" on public.pedidos
as restrictive
for all to authenticated
using ( not exists (
  select 1 from public.suspendidos where user_id = (select auth.uid())
));

Úsalas con cuidado: una restrictiva mal escrita bloquea a todos, incluidos tus administradores.

Lo que RLS no cubre

  • La llave service_role se salta RLS por completo. Solo debe vivir en tu servidor o en Edge Functions, nunca en el frontend. Si alguna vez la pegaste en el código del navegador, revisa qué hacer si tu service role key quedó expuesta.
  • Funciones con security definer también se saltan RLS; revisa que validen al usuario por dentro.
  • Validaciones de negocio (que un precio no sea negativo, que un pedido no se pague dos veces) van en restricciones de la tabla o en tu backend, no solo en políticas.

Cómo probar tus políticas

  1. Dos usuarios de prueba; crea datos con uno, intenta verlos y editarlos con el otro.
  2. Prueba también sin sesión (como anon).
  3. Corre Advisors → Security Advisor en Supabase.
  4. Si algo legítimo falla con “new row violates row-level security policy”, ve la explicación de ese error.

En resumen

La mayoría de las apps necesita pocas combinaciones: “cada quien lo suyo”, “público solo lectura”, “admins ven todo” y “miembros del equipo”. Usa using para filas existentes, with check para filas nuevas, y prueba con dos cuentas. Si tu app tiene reglas de acceso más complicadas o quieres que alguien revise tus políticas antes de publicar, en Black Engine lo revisamos contigo por WhatsApp; también puedes empezar por nuestra checklist de seguridad para apps hechas con IA.

Preguntas frecuentes

¿Cuál es la diferencia entre using y with check?

using filtra las filas que ya existen (qué puedes ver, editar o borrar). with check valida las filas nuevas o el resultado de una edición (qué puedes crear o en qué puede quedar una fila).

¿Puedo tener varias políticas en la misma tabla?

Sí. Las políticas permisivas se combinan con OR: si cualquiera permite la acción, se permite. Por eso una política demasiado abierta anula a las estrictas.

¿Cómo hago una política para administradores?

Guarda los roles en una tabla que los usuarios no puedan editar y consulta esa tabla desde la política, de preferencia con una función. Nunca te bases en un campo que el usuario puede cambiar.

¿Las políticas RLS hacen más lenta mi app?

Pueden, si consultan mucho. Supabase recomienda envolver auth.uid() en (select auth.uid()) y poner índices en las columnas que usan las políticas.

Pide ayuda por WhatsApp