agentleFS
charanjit-singhcharanjit-singh/lovable-skills/skills/multi-tenant-backend/SKILL.md

multi-tenant-backend

Skill10 starsChanged 6 months agoMITView on GitHub

Use when building organization/workspace multi-tenancy: teams, memberships, roles, inviting members, or scoping all data by org_id. Not for single-user apps with only user_id ownership.

The file

We split this skill into the rules it gives an agent

4 rules. About 0 are common, 2 are distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    All tenant data includes organization_id uuid not null references public.organizations(id).

    line 29
  2. Very specific:

    Avoid recursion: if memberships itself has RLS that references memberships, policies can recurse.

    line 33
  3. Distinctive:

    Ensure signed-in user email matches (or allow any authenticated accept per product rule).

    line 84
  4. Distinctive:

    Never expose invite tokens in logs.

    line 87

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Multi-tenant backend

Core tables

create table public.organizations (
  id uuid primary key default gen_random_uuid(),
  name text not null,
  slug text unique,
  created_at timestamptz default now()
);

create table public.memberships (
  organization_id uuid references public.organizations(id) on delete cascade,
  user_id uuid references auth.users(id) on delete cascade,
  role text not null check (role in ('owner','admin','member')),
  primary key (organization_id, user_id)
);

All tenant data includes organization_id uuid not null references public.organizations(id).

RLS pattern

Avoid recursion: if memberships itself has RLS that references memberships, policies can recurse. Use a security definer helper that bypasses RLS for the lookup:

create or replace function public.is_org_member(p_org uuid)
returns boolean
language sql security definer set search_path = public stable as $$
  select exists (
    select 1 from public.memberships
    where organization_id = p_org and user_id = auth.uid()
  );
$$;

create or replace function public.has_org_role(p_org uuid, p_roles text[])
returns boolean
language sql security definer set search_path = public stable as $$
  select exists (
    select 1 from public.memberships
    where organization_id = p_org
      and user_id = auth.uid()
      and role = any(p_roles)
  );
$$;

revoke all on function public.is_org_member(uuid) from public;
revoke all on function public.has_org_role(uuid, text[]) from public;
grant execute on function public.is_org_member(uuid) to authenticated;
grant execute on function public.has_org_role(uuid, text[]) to authenticated;

Then policies are simple and recursion-safe:

create policy "members_select_org_projects" on public.projects
  for select using (public.is_org_member(organization_id));

create policy "admins_update_projects" on public.projects
  for update using (public.has_org_role(organization_id, array['owner','admin']));

On memberships itself, scope policies to the caller's own row to avoid recursion:

create policy "members_select_own_membership" on public.memberships
  for select using (user_id = auth.uid());

Invites

invites(email, organization_id, role, token_hash, expires_at) — accept via edge function:

  1. Validate token, not expired.
  2. Ensure signed-in user email matches (or allow any authenticated accept per product rule).
  3. Insert memberships; delete invite.

Never expose invite tokens in logs.

Active org context

Client stores active_organization_id (profile or localStorage); every query filters by it. Server still enforces RLS — client filter is UX only.

Avoid

  • Tenant id only in React state without RLS.
  • Shared organization_id in URLs without membership check.
  • owner role without transfer-ownership flow.

Checklist

  • Every tenant table has organization_id + index
  • RLS on all tenant tables
  • Invite accept is server-mediated
  • Test user A cannot read org B (supabase-rls-and-auth)

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.