Organizations

Multi-tenant organization management. Create teams, manage members with granular roles, and build collaborative features.

Overview

Organizations enable multi-tenant functionality in your application. Users can create and join multiple organizations, each with its own members, roles, and settings.

Multi-Tenant

Users can belong to multiple organizations with different roles

Granular Roles

Six built-in roles from super_admin to viewer

Member Management

Invite, remove, and manage team members

Invitations

Email-based invites with role assignment

Organization Settings

Customizable profile, branding, and preferences

Role-Based Access

Control access to features based on member roles

Quick Start

Access the current organization and user membership in any component:

Using Organization Hooks
import { useOrganization, useOrganizations } from '@sylphx/platform-sdk/react'

function Dashboard() {
  // Current active organization
  const {
    organization,    // Current org data
    membership,      // User's membership in this org
    role,           // User's role (e.g., 'admin')
    isLoading,
    isAdmin,        // Helper: role is admin or super_admin
  } = useOrganization()

  // All organizations user belongs to
  const {
    organizations,   // List of all orgs
    setActive,      // Switch active organization
    create,         // Create new organization
  } = useOrganizations()

  if (isLoading) return <Loading />

  return (
    <div>
      <h1>Welcome to {organization?.name}</h1>
      <p>Your role: {role}</p>

      {isAdmin && (
        <Link href="/settings">Manage Organization</Link>
      )}
    </div>
  )
}

Active Organization

The SDK automatically tracks the active organization. When users switch organizations, all hooks and components update automatically.

Organization Roles

Six predefined roles provide granular access control. Each role inherits permissions from roles below it in the hierarchy.

super_admin

Full access to all organization settings, billing, and member management

All permissionsDelete organizationTransfer ownership
admin

Manage members, settings, and most organization features

Manage membersUpdate settingsView analyticsManage integrations
billing

Access to billing, invoices, and subscription management

View/update billingManage subscriptionsDownload invoices
analytics

View analytics dashboards and export reports

View analyticsExport reportsCreate dashboards
developer

Access to API keys, webhooks, and technical configuration

Manage API keysConfigure webhooksView logs
viewer

Read-only access to organization resources

View resourcesView membersView basic analytics
Checking Roles
import { useOrganization } from '@sylphx/platform-sdk/react'

function AdminPanel() {
  const { role, isAdmin, hasPermission } = useOrganization()

  // Check specific role
  if (role === 'super_admin') {
    // Full access
  }

  // Check admin-level access (super_admin or admin)
  if (isAdmin) {
    // Can manage members and settings
  }

  // Check specific permission
  if (hasPermission('billing:read')) {
    // Can view billing info
  }

  return (
    <div>
      {hasPermission('members:write') && (
        <InviteMemberButton />
      )}
    </div>
  )
}

useOrganization Hook

Access the current active organization and the user's membership details.

PropertyTypeDescription
organizationOrganization | nullCurrent active organization data
membershipMembership | nullUser's membership in the organization
roleOrganizationRole | nullUser's role in the organization
isLoadingbooleanLoading state
isAdminbooleanTrue if role is super_admin or admin
isSuperAdminbooleanTrue if role is super_admin
hasPermission(permission: string) => booleanCheck if user has specific permission
update(data: UpdateOrgData) => Promise<void>Update organization details
inviteMember(email: string, role: Role) => Promise<void>Invite a new member
removeMember(userId: string) => Promise<void>Remove a member from organization
updateMemberRole(userId: string, role: Role) => Promise<void>Update a member's role
leave() => Promise<void>Leave the organization
Managing Members
import { useOrganization } from '@sylphx/platform-sdk/react'

function MemberManagement() {
  const {
    organization,
    inviteMember,
    removeMember,
    updateMemberRole,
    isAdmin,
  } = useOrganization()

  const handleInvite = async (email: string) => {
    await inviteMember(email, 'developer')
    toast.success('Invitation sent!')
  }

  const handleRoleChange = async (userId: string, newRole: string) => {
    await updateMemberRole(userId, newRole)
    toast.success('Role updated!')
  }

  const handleRemove = async (userId: string) => {
    if (confirm('Remove this member?')) {
      await removeMember(userId)
      toast.success('Member removed')
    }
  }

  return (
    <div>
      <h2>Members ({organization?.members.length})</h2>

      {organization?.members.map((member) => (
        <div key={member.id}>
          <span>{member.user.email}</span>
          <span>{member.role}</span>

          {isAdmin && (
            <>
              <RoleSelect
                value={member.role}
                onChange={(role) => handleRoleChange(member.userId, role)}
              />
              <button onClick={() => handleRemove(member.userId)}>
                Remove
              </button>
            </>
          )}
        </div>
      ))}
    </div>
  )
}

useOrganizations Hook

Manage all organizations the user belongs to and switch between them.

PropertyTypeDescription
organizationsOrganization[]All organizations user belongs to
activeOrganizationOrganization | nullCurrently active organization
isLoadingbooleanLoading state
setActive(orgId: string) => Promise<void>Switch active organization
create(data: CreateOrgData) => Promise<Organization>Create a new organization
delete(orgId: string) => Promise<void>Delete an organization (super_admin only)
Organization Switcher
import { useOrganizations } from '@sylphx/platform-sdk/react'

function OrganizationSwitcher() {
  const {
    organizations,
    activeOrganization,
    setActive,
    create,
    isLoading,
  } = useOrganizations()

  const handleSwitch = async (orgId: string) => {
    await setActive(orgId)
    // App context updates automatically
    toast.success('Switched organization')
  }

  const handleCreate = async () => {
    const org = await create({
      name: 'New Organization',
      slug: 'new-org',
    })
    // Automatically becomes active
    router.push('/settings/organization')
  }

  if (isLoading) return <Spinner />

  return (
    <Dropdown>
      <DropdownTrigger>
        <Avatar src={activeOrganization?.logoUrl} />
        <span>{activeOrganization?.name}</span>
      </DropdownTrigger>

      <DropdownContent>
        {organizations.map((org) => (
          <DropdownItem
            key={org.id}
            onClick={() => handleSwitch(org.id)}
            active={org.id === activeOrganization?.id}
          >
            <Avatar src={org.logoUrl} size="sm" />
            <span>{org.name}</span>
          </DropdownItem>
        ))}

        <DropdownSeparator />

        <DropdownItem onClick={handleCreate}>
          <Plus className="w-4 h-4" />
          Create Organization
        </DropdownItem>
      </DropdownContent>
    </Dropdown>
  )
}

Organization Data Model

Understanding the organization data structure helps when building custom UI.

Type Definitions
interface Organization {
  id: string
  name: string
  slug: string
  logoUrl?: string
  description?: string
  website?: string
  createdAt: Date
  updatedAt: Date

  // Populated when fetching with members
  members?: Membership[]
  invitations?: Invitation[]

  // Organization settings
  settings: {
    defaultRole: OrganizationRole
    allowMemberInvites: boolean
    requireEmailDomain?: string
  }
}

interface Membership {
  id: string
  userId: string
  organizationId: string
  role: OrganizationRole
  joinedAt: Date
  user: {
    id: string
    email: string
    name?: string
    avatarUrl?: string
  }
}

type OrganizationRole =
  | 'super_admin'
  | 'admin'
  | 'billing'
  | 'analytics'
  | 'developer'
  | 'viewer'

interface Invitation {
  id: string
  email: string
  role: OrganizationRole
  status: 'pending' | 'accepted' | 'expired'
  expiresAt: Date
  invitedBy: string
}

Server-Side Usage

Access organization data in Server Components and API routes.

import { currentOrganization, auth } from '@sylphx/platform-sdk/nextjs'
import { redirect } from 'next/navigation'

export default async function OrganizationPage() {
  const org = await currentOrganization()

  if (!org) {
    redirect('/create-organization')
  }

  return (
    <div>
      <h1>{org.name}</h1>
      <p>Members: {org.members?.length}</p>
    </div>
  )
}

Organization Context

The SDK stores the active organization ID in a secure cookie. Server-side functions automatically use this to determine the current organization context.

Invitations

Handle organization invitations with built-in email sending and acceptance flow.

Invitation Flow
import { useOrganization } from '@sylphx/platform-sdk/react'

function InviteMembers() {
  const { inviteMember, organization } = useOrganization()
  const [email, setEmail] = useState('')
  const [role, setRole] = useState<OrganizationRole>('developer')

  const handleInvite = async (e: FormEvent) => {
    e.preventDefault()

    try {
      await inviteMember(email, role)
      toast.success(`Invitation sent to ${email}`)
      setEmail('')
    } catch (error) {
      if (error.code === 'ALREADY_MEMBER') {
        toast.error('User is already a member')
      } else if (error.code === 'INVITATION_EXISTS') {
        toast.error('Invitation already sent')
      } else {
        toast.error('Failed to send invitation')
      }
    }
  }

  return (
    <form onSubmit={handleInvite}>
      <Input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="colleague@company.com"
      />

      <Select value={role} onChange={setRole}>
        <option value="admin">Admin</option>
        <option value="developer">Developer</option>
        <option value="viewer">Viewer</option>
      </Select>

      <Button type="submit">Send Invitation</Button>

      {/* Pending invitations */}
      <div className="mt-4">
        <h3>Pending Invitations</h3>
        {organization?.invitations
          ?.filter(i => i.status === 'pending')
          .map((invitation) => (
            <div key={invitation.id}>
              <span>{invitation.email}</span>
              <span>{invitation.role}</span>
              <span>Expires {formatDate(invitation.expiresAt)}</span>
            </div>
          ))
        }
      </div>
    </form>
  )
}

Pre-built Organization Components

Use our ready-made components for organization management, member lists, and organization switching.

View Components