Teams & Workspaces
Multi-tenant organizations with member invitations, roles, and per-plan limits.
TheShipStack is multi-tenant from the start. Every user belongs to one or more workspaces (organizations). All data — projects, billing, and members — is scoped to a workspace.
Concepts
- Workspace — an organization that groups users and resources together
- Member — a user who belongs to a workspace with a specific role
- Role —
owner,admin, ormember(see RBAC) - Active workspace — the workspace a user is currently acting in
Creating a workspace
Users create a workspace during onboarding. They can create additional workspaces from the dashboard. Each workspace is independent — separate billing, members, and data.
Inviting members
Owners and admins can invite members from /dashboard/team. An invitation email is sent via Resend. When accepted, the user joins the workspace with the assigned role.
import { inviteMember } from '@/actions/team'
await inviteMember('colleague@example.com', 'admin')Invitations check the plan's member limit before sending — if the workspace is at its limit, UPGRADE_REQUIRED is thrown.
Managing members
import { updateMemberRole, removeMember } from '@/actions/team'
await updateMemberRole(memberId, 'admin') // promote/demote
await removeMember(memberId) // remove from workspaceAffected members receive a notification when their role changes or they are removed.
Per-plan member limits
Member limits are defined in lib/plans.ts alongside project limits. Free workspaces have a fixed cap; paid plans raise or remove the limit.
Switching workspaces
The active workspace is stored in the session. Users switch workspaces via the workspace switcher in the sidebar. All data reads use the active organization ID.
Accessing the current workspace in server code
import { requireOrg } from '@/lib/require-org'
const { orgId } = await requireOrg()
// orgId is the active workspace — throws if not authenticated or no orgSchema
Workspace data is managed by Better Auth's organization plugin. The relevant tables are:
organization— workspace name, logo, slugmember— user↔workspace relationship with roleinvitation— pending invitations
These are created automatically by Better Auth when you run pnpm db:push.