Multi-Tenancy & Team Management
Workspace isolation, PostgreSQL Row Level Security, team invitations, and role-based access control for B2B SaaS applications.
On this page
What's Included
The LaunchSaaS multi-tenancy package handles the organizational layer of a B2B SaaS application. Each customer account is a workspace — an isolated container for that organization's data, users, and settings.
- Workspace creation — users create a workspace on signup or can be invited to one
- Workspace isolation — each workspace's data is completely isolated from others
- PostgreSQL RLS — isolation enforced at the database level, not the application layer
- Team invitations — invite by email; works for new and existing users
- RBAC — three built-in roles: admin, member, and viewer
- Multi-workspace membership — a user can belong to multiple workspaces with different roles
- Workspace settings UI — name, logo, billing, and member management
How Data Isolation Works
LaunchSaaS uses PostgreSQL Row Level Security (RLS) to enforce tenant isolation. This is the most secure approach because it is enforced at the database level — even application-layer bugs cannot cause data leakage between tenants.
How RLS policies work
Every table that stores workspace-scoped data has a workspace_id column and RLS policies like this:
-- Only allow access to rows in the user's workspace
CREATE POLICY workspace_isolation ON workspace_resources
USING (
workspace_id IN (
SELECT workspace_id FROM workspace_members
WHERE user_id = auth.uid()
)
);
When a user's request hits Supabase, Postgres evaluates this policy on every query. Rows belonging to other workspaces are simply not returned — they do not appear in results and cannot be modified.
The workspace_members table
The central relationship is the workspace_members table, which maps users to workspaces with their role:
workspace_members
├── id (uuid)
├── workspace_id (uuid, FK → workspaces)
├── user_id (uuid, FK → auth.users)
├── role (enum: admin | member | viewer)
└── created_at (timestamptz)
RLS policies on all other tables reference this table to determine access.
Roles and Permissions
| Action | Admin | Member | Viewer |
|---|---|---|---|
| View workspace resources | ✓ | ✓ | ✓ |
| Create & edit resources | ✓ | ✓ | — |
| Delete resources | ✓ | ✓ | — |
| Invite team members | ✓ | — | — |
| Remove team members | ✓ | — | — |
| Change member roles | ✓ | — | — |
| Manage billing | ✓ | — | — |
| Update workspace settings | ✓ | — | — |
| Delete workspace | ✓ | — | — |
Role checks are enforced in two places: the UI (buttons are hidden or disabled based on role) and the API layer via a requireRole() utility that returns a 403 if the user lacks the required role. RLS policies additionally enforce admin-only data access at the database level.
Team Invitations
Workspace admins can invite new team members by email address. The invitation flow:
- Admin enters an email address and selects a role in the workspace settings.
- LaunchSaaS creates a pending invitation record in the
workspace_invitationstable. - An invitation email is sent via Resend with a unique invite link.
- If the invitee has an account, clicking the link adds them to the workspace immediately.
- If the invitee is new, they are prompted to sign up. After signup, the invitation is applied automatically.
- Invitations expire after 7 days (configurable). Admins can resend or cancel pending invitations.
Extending with Custom Roles
The three built-in roles cover most B2B SaaS use cases, but you can add custom roles if needed.
Adding a "billing_manager" role
-
Database migration: Add the new value to the role enum.
ALTER TYPE workspace_role ADD VALUE 'billing_manager'; -
Update TypeScript types in
types/database.ts:export type WorkspaceRole = 'admin' | 'member' | 'viewer' | 'billing_manager'; -
Add permissions in
lib/auth/roles.ts:case 'billing_manager': return ['view_resources', 'manage_billing']; - Update RLS policies if the new role needs different database access than existing roles.
Frequently Asked Questions
Does LaunchSaaS support B2B multi-tenant SaaS with team workspaces?
Yes. LaunchSaaS includes a complete multi-tenancy package designed for B2B SaaS. Each customer organization gets a workspace with isolated data, team member management, and role-based access control. A user can be a member of multiple workspaces with different roles in each.
How does LaunchSaaS handle data isolation between tenants?
Data isolation is enforced at the PostgreSQL level using Row Level Security (RLS) policies. Every tenant-scoped table has policies that only allow access to rows where the workspace_id matches a workspace the authenticated user belongs to. Even a bug in the application layer cannot accidentally return one tenant's data to another tenant.
What roles are available in LaunchSaaS RBAC and what can each role do?
LaunchSaaS includes three roles: admin, member, and viewer. Admins can manage team members, update billing, change workspace settings, and perform all member/viewer actions. Members can create and edit workspace resources. Viewers have read-only access. Role checks are enforced at both the UI and RLS levels.
How does team invitation work in LaunchSaaS — can I invite someone who doesn't have an account yet?
Yes. Team invitations work for both existing and new users. When you invite someone by email, LaunchSaaS creates a pending invitation and sends an invitation email. If the invitee is new, they are prompted to sign up first, then the invitation is automatically applied. Invitations expire after 7 days by default.
Can a single user belong to multiple workspaces in LaunchSaaS?
Yes. A single user can be a member of multiple workspaces with different roles in each. The active workspace is stored in the user's session and can be switched from the workspace selector. All workspace-scoped queries automatically use the active workspace ID.
How do I add a custom role beyond admin, member, and viewer in LaunchSaaS?
Roles are stored as a database enum and checked via lib/auth/roles.ts. To add a custom role: add the value to the enum in a migration, update the TypeScript type, add the new role's permissions to hasPermission(), and update any RLS policies that need different data access for the new role.
Ready to ship
Skip the boilerplate. Ship your product.
14 production packages. 2,335 tests. Battle-tested by 13,000+ users. One-time payment. Lifetime access.
Get Instant Access — $99