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.
In this repository56 other files it gives its agents.
- accessibility-passSkill
- analytics-eventsSkill
- api-error-handlingSkill
- audit-logging-backendSkill
- background-jobs-and-cronSkill
- dark-mode-and-themingSkill
- dashboard-charts-rechartsSkill
- edge-functions-and-webhooksSkill
- email-and-notificationsSkill
- error-states-and-empty-uiSkill
- feature-flags-and-togglesSkill
- file-upload-storageSkill
- i18n-multi-languageSkill
- lovable-prompting-playbookSkill
- lovable-ship-checklistSkill
- migration-playbookSkill
- onboarding-copySkill
- optimistic-updatesSkill
- payment-failed-and-recoverySkill
- payment-webhook-idempotencySkill
- payment-webhook-testingSkill
- performance-budgetSkill
- pg-cron-recipesSkill
- pg-cron-scheduled-jobsSkill
- pg-cron-with-pg-net-edgeSkill
- postgres-full-text-searchSkill
- postgres-triggers-and-functionsSkill
- pricing-and-billingSkill
- programmatic-seo-content-qualitySkill
- programmatic-seo-pagesSkill
- rate-limiting-edgeSkill
- realtime-and-subscriptionsSkill
- refactor-safe-diffSkill
- release-notesSkill
- seo-core-web-vitalsSkill
- seo-landing-pageSkill
- seo-meta-tags-spaSkill
- seo-sitemap-generationSkill
- seo-ssr-and-prerenderingSkill
- seo-structured-dataSkill
- server-input-validationSkill
- shadcn-patternsSkill
- soft-delete-and-archivingSkill
- stripe-one-time-payment-webhooksSkill
- stripe-payment-webhooksSkill
- supabase-auth-flowsSkill
- supabase-database-designSkill
- supabase-rls-and-authSkill
- support-replySkill
- tanstack-infinite-queriesSkill
- tanstack-mutations-and-invalidationSkill
- tanstack-prefetch-and-hydrationSkill
- tanstack-query-alternativeSkill
- tanstack-router-data-loadersSkill
- tanstack-start-server-routesSkill
- typed-api-hooks-formsSkill
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
- Very specific: line 29
All tenant data includes organization_id uuid not null references public.organizations(id).
- Very specific: line 33
Avoid recursion: if memberships itself has RLS that references memberships, policies can recurse.
- Distinctive: line 84
Ensure signed-in user email matches (or allow any authenticated accept per product rule).
- Distinctive: line 87
Never expose invite tokens in logs.
≈ “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.
Files
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:
- Validate token, not expired.
- Ensure signed-in user email matches (or allow any authenticated accept per product rule).
- 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_idin URLs without membership check. ownerrole 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)
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
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.

