ScaleRocket/Web

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

FeaturemultiTenant: falsemultiTenant: true
SidebarDashboard, Settings, BillingDashboard, Team, Settings, Billing
CreditsPer userPer organization
BillingPer userPer organization (owner pays)
Data accessUser sees own dataUser sees org's data
Org switcherHiddenVisible in sidebar

Database Schema

Two tables are added by the 00002_multi_tenant.sql migration:

organizations

ColumnTypeDescription
idUUIDPrimary key
nameTEXTOrganization display name
slugTEXTUnique URL-friendly identifier
owner_idUUIDFK to auth.users — the billing owner
plan_idTEXTCurrent subscription plan
stripe_customer_idTEXTStripe customer for org billing
stripe_subscription_idTEXTStripe subscription ID
avatar_urlTEXTOrganization logo

memberships

ColumnTypeDescription
idUUIDPrimary key
organization_idUUIDFK to organizations
user_idUUIDFK to auth.users (null if pending invite)
invited_emailTEXTEmail for pending invitations
roleTEXTowner, admin, or member
statusTEXTpending 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

RoleCan inviteCan remove membersCan change rolesCan update orgCan delete orgCan manage billing
OwnerYesYesYesYesYesYes
AdminYesYesNoYesNoNo
MemberNoNoNoNoNoNo

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_id column to the credits table 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.

On this page