If your app feels solid in testing but “random” with real users, it’s usually not the model. It’s permissions living in prompts instead of being enforced by the database. Below is a copyable Supabase RLS setup for an owner-owned object shared with teammates.
Supabase RLS example: Projects owned by a user, shared with teammates
Assumptions
• projects.owner\_id is the creator
• optional join table project\_members allows sharing
• users can read a project if they own it or they’re a member
• only owners can update/delete
• members can’t update the project row (extend later if you need role-based edits)
\-- Projects
create table if not exists public.projects (
id uuid primary key default gen\_random\_uuid(),
owner\_id uuid not null references auth.users(id),
title text not null,
status text not null default 'active',
created\_at timestamptz not null default now(),
updated\_at timestamptz not null default now()
);
\-- Optional: membership for sharing projects
create table if not exists public.project\_members (
project\_id uuid not null references public.projects(id) on delete cascade,
user\_id uuid not null references auth.users(id) on delete cascade,
role text not null default 'member',
created\_at timestamptz not null default now(),
primary key (project\_id, user\_id)
);
\-- Helpful indexes
create index if not exists projects\_owner\_id\_idx on public.projects(owner\_id);
create index if not exists project\_members\_user\_id\_idx on public.project\_members(user\_id);
alter table public.projects enable row level security;
alter table public.project\_members enable row level security;
Advanced_Pudding9228 shares a detailed setup for implementing Row Level Security (RLS) in Supabase for projects owned by users and shared with teammates. The setup includes SQL code for creating tables, enabling RLS, and defining policies to manage permissions. Illustrious-Mail-587 suggests using Nuvix as an alternative solution.
This is unnecessary. Simply use Nuvix, as it handles everything end to end.
Yea as long as they use NUVIX
Projects: read if owner or member
create policy "projects\_select\_owner\_or\_member"
on public.projects
for select
to authenticated
using (
owner\_id = auth.uid()
or exists (
select 1
from public.project\_members pm
where pm.project\_id = projects.id
and pm.user\_id = auth.uid()
)
);
Projects: insert only as self (prevents spoofing owner\_id)
create policy "projects\_insert\_owner\_is\_self"
on public.projects
for insert
to authenticated
with check (
owner\_id = auth.uid()
);
Projects: update/delete only owner
create policy "projects\_update\_owner\_only"
on public.projects
for update
to authenticated
using (owner\_id = auth.uid())
with check (owner\_id = auth.uid());
create policy "projects\_delete\_owner\_only"
on public.projects
for delete
to authenticated
using (owner\_id = auth.uid());
Project members: owners manage membership, members can read their own membership rows
create policy "project\_members\_select\_self\_or\_owner"
on public.project\_members
for select
to authenticated
using (
user\_id = auth.uid()
or exists (
select 1
from public.projects p
where p.id = project\_members.project\_id
and p.owner\_id = auth.uid()
)
);
create policy "project\_members\_insert\_owner\_only"
on public.project\_members
for insert
to authenticated
with check (
exists (
select 1
from public.projects p
where p.id = project\_members.project\_id
and p.owner\_id = auth.uid()
)
);
create policy "project\_members\_delete\_owner\_only"
on public.project\_members
for delete
to authenticated
using (
exists (
select 1
from public.projects p
where p.id = project\_members.project\_id
and p.owner\_id = auth.uid()
)
);