Multi-Tenant (Organizations)
Enable team workspaces with organizations, member invitations, roles, and shared billing.
Multi-Tenant (Organizations)
ScaleRocket includes optional multi-tenant support. When enabled, users can create organizations, invite team members, assign roles, and share billing under one plan.
Enable Multi-Tenant
Set the flag in your site config:
// packages/config/src/site.ts
export const siteConfig = {
multiTenant: true, // ← set to true
// ...
};Then enable the auto-create trigger in your Supabase SQL editor:
CREATE TRIGGER on_auth_user_created_org
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user_org();This automatically creates a personal organization when a new user signs up.
What Changes
| Feature | multiTenant: false | multiTenant: true |
|---|---|---|
| Sidebar | Dashboard, Settings, Billing | Dashboard, Team, Settings, Billing |
| Credits | Per user | Per organization |
| Billing | Per user | Per organization (owner pays) |
| Data access | User sees own data | User sees org's data |
| Org switcher | Hidden | Visible in sidebar |
Database Schema
Two tables are added by the 00002_multi_tenant.sql migration:
organizations
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
name | TEXT | Organization display name |
slug | TEXT | Unique URL-friendly identifier |
owner_id | UUID | FK to auth.users — the billing owner |
plan_id | TEXT | Current subscription plan |
stripe_customer_id | TEXT | Stripe customer for org billing |
stripe_subscription_id | TEXT | Stripe subscription ID |
avatar_url | TEXT | Organization logo |
memberships
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
organization_id | UUID | FK to organizations |
user_id | UUID | FK to auth.users (null if pending invite) |
invited_email | TEXT | Email for pending invitations |
role | TEXT | owner, admin, or member |
status | TEXT | pending or active |
RLS Policies
The migration includes comprehensive RLS policies:
- Organizations: members can view their orgs, only owners can update/delete
- Memberships: members can view org memberships, admins/owners can invite/remove, users can accept their own invitations and leave
Roles
| Role | Can invite | Can remove members | Can change roles | Can update org | Can delete org | Can manage billing |
|---|---|---|---|---|---|---|
| Owner | Yes | Yes | Yes | Yes | Yes | Yes |
| Admin | Yes | Yes | No | Yes | No | No |
| Member | No | No | No | No | No | No |
Edge Function: manage-organization
All team operations go through a single Edge Function with an action parameter:
// Available actions:
await callFunction("manage-organization", {
method: "POST",
body: { action: "create", name: "My Company", slug: "my-company" },
});
await callFunction("manage-organization", {
method: "POST",
body: { action: "invite", organizationId: "...", email: "john@example.com", role: "member" },
});
await callFunction("manage-organization", {
method: "POST",
body: { action: "accept-invite", membershipId: "..." },
});
await callFunction("manage-organization", {
method: "POST",
body: { action: "remove-member", membershipId: "..." },
});
await callFunction("manage-organization", {
method: "POST",
body: { action: "update-role", membershipId: "...", role: "admin" },
});
await callFunction("manage-organization", {
method: "POST",
body: { action: "delete-org", organizationId: "..." },
});Frontend Components
Org Switcher (Sidebar)
When multiTenant is enabled, the sidebar shows an org switcher above the navigation links. Users can:
- See their current organization
- Switch between organizations
- Create a new organization
The current org ID is stored in localStorage and persists across sessions.
Team Page (/team)
The team management page includes:
- Member list with role badges and status indicators
- Invite form for adding new members by email
- Role management for owners to change member roles
- Remove member for owners/admins to remove team members
- Leave organization for members to leave voluntarily
Adapting Credits for Orgs
When multi-tenant is enabled, you'll want credits to be shared across the organization instead of per-user. Update your credit queries:
// Single-tenant: credits per user
const { data: credits } = await supabase
.from("credits")
.select("*")
.eq("user_id", user.id)
.single();
// Multi-tenant: credits per organization
const { data: credits } = await supabase
.from("credits")
.select("*")
.eq("organization_id", currentOrgId)
.single();Note: You'll need to add an
organization_idcolumn to thecreditstable and update the RLS policies accordingly when switching to org-level billing.
Next Steps
- Authentication — how auth works with organizations
- Payments — Stripe billing per organization
- Credits — adapting the credit system for teams
- Deployment — deploy with multi-tenant enabled
Done reading? Mark this page as complete.