Team Workspace API | Next.js Supabase SaaS Kit
Access team account context in MakerKit layouts. Load team data, member permissions, subscription status, and role hierarchy with the Team Workspace API.
The Team Workspace API provides team account context for pages under /home/[account]. It loads team data, the user's role and permissions, subscription status, and all accounts the user belongs to, making this information available to both server and client components.
Team Workspace API Reference
Access team workspace data in layouts and components
loadTeamWorkspace (Server)
Loads the team workspace data for the specified team account. Use this in Server Components within the /home/[account] route group.
import { loadTeamWorkspace } from '~/home/[account]/_lib/server/team-account-workspace.loader';export default async function TeamDashboard({ params,}: { params: { account: string };}) { const data = await loadTeamWorkspace(); return ( <div> <h1>{data.account.name}</h1> <p>Your role: {data.account.role}</p> </div> );}Function signature
async function loadTeamWorkspace(): Promise<TeamWorkspaceData>How it works
The loader reads the account parameter from the URL (the team slug) and fetches:
- Team account details from the database
- Current user's role and permissions in this team
- All accounts the user belongs to (for the account switcher)
Caching behavior
The function uses React's cache() to deduplicate calls within a single request. You can call it multiple times in nested components without additional database queries.
// Both calls use the same cached dataconst layout = await loadTeamWorkspace(); // First call: hits databaseconst page = await loadTeamWorkspace(); // Second call: returns cached dataWhile calls are deduplicated within a request, the data is fetched on every navigation. For frequently accessed data, the caching prevents redundant queries within a single page render.
useTeamAccountWorkspace (Client)
Access the team workspace data in client components using the useTeamAccountWorkspace hook. The data is provided through React Context from the layout.
'use client';import { useTeamAccountWorkspace } from '@kit/team-accounts/hooks/use-team-account-workspace';export function TeamHeader() { const { account, user, accounts } = useTeamAccountWorkspace(); return ( <header className="flex items-center justify-between p-4"> <div className="flex items-center gap-3"> {account.picture_url && ( <img src={account.picture_url} alt={account.name} className="h-8 w-8 rounded" /> )} <div> <h1 className="font-semibold">{account.name}</h1> <p className="text-xs text-muted-foreground"> {account.role} · {account.subscription_status || 'Free'} </p> </div> </div> </header> );}The useTeamAccountWorkspace hook only works within the /home/[account] route group where the context provider is set up. Using it outside this layout will throw an error.
Data structure
TeamWorkspaceData
import type { User } from '@supabase/supabase-js';interface TeamWorkspaceData { account: { id: string; name: string; slug: string; picture_url: string | null; role: string; role_hierarchy_level: number; primary_owner_user_id: string; subscription_status: SubscriptionStatus | null; permissions: string[]; }; user: User; accounts: Array<{ id: string | null; name: string | null; picture_url: string | null; role: string | null; slug: string | null; }>;}account.role
The user's role in this team. Default roles:
| Role | Description |
|---|---|
owner | Full access, can delete team |
admin | Manage members and settings |
member | Standard access |
account.role_hierarchy_level
A numeric value where lower numbers indicate higher privilege. Use this for role comparisons:
const { account } = useTeamAccountWorkspace();// Check if user can manage someone with role_level 2const canManage = account.role_hierarchy_level < 2;account.permissions
An array of permission strings the user has in this team:
[ 'billing.manage', 'members.invite', 'members.remove', 'members.manage', 'settings.manage',]subscription_status values
| Status | Description |
|---|---|
active | Active subscription |
trialing | In trial period |
past_due | Payment failed, grace period |
canceled | Subscription canceled |
unpaid | Payment required |
incomplete | Setup incomplete |
incomplete_expired | Setup expired |
paused | Subscription paused |
Usage patterns
Permission-based rendering
'use client';import { useTeamAccountWorkspace } from '@kit/team-accounts/hooks/use-team-account-workspace';interface PermissionGateProps { children: React.ReactNode; permission: string; fallback?: React.ReactNode;}export function PermissionGate({ children, permission, fallback = null,}: PermissionGateProps) { const { account } = useTeamAccountWorkspace(); if (!account.permissions.includes(permission)) { return <>{fallback}</>; } return <>{children}</>;}// Usagefunction TeamSettingsPage() { return ( <div> <h1>Team Settings</h1> <PermissionGate permission="settings.manage" fallback={<p>You don't have permission to manage settings.</p>} > <SettingsForm /> </PermissionGate> <PermissionGate permission="billing.manage"> <BillingSection /> </PermissionGate> </div> );}Team dashboard with role checks
import { loadTeamWorkspace } from '~/home/[account]/_lib/server/team-account-workspace.loader';import { getSupabaseServerClient } from '@kit/supabase/server-client';export default async function TeamDashboardPage() { const { account, user } = await loadTeamWorkspace(); const client = getSupabaseServerClient(); const isOwner = account.primary_owner_user_id === user.id; const isAdmin = account.role === 'admin' || account.role === 'owner'; // Fetch team-specific data const { data: projects } = await client .from('projects') .select('*') .eq('account_id', account.id) .order('created_at', { ascending: false }) .limit(10); return ( <div className="space-y-6"> <header className="flex items-center justify-between"> <div> <h1 className="text-2xl font-bold">{account.name}</h1> <p className="text-muted-foreground"> {account.subscription_status === 'active' ? 'Pro Plan' : 'Free Plan'} </p> </div> {isAdmin && ( <a href={`/home/${account.slug}/settings`} className="btn btn-secondary" > Team Settings </a> )} </header> <section> <h2 className="text-lg font-medium">Recent Projects</h2> <ul className="mt-2 space-y-2"> {projects?.map((project) => ( <li key={project.id}> <a href={`/home/${account.slug}/projects/${project.id}`}> {project.name} </a> </li> ))} </ul> </section> {isOwner && ( <section className="rounded-lg border border-destructive/20 bg-destructive/5 p-4"> <h2 className="font-medium text-destructive">Danger Zone</h2> <p className="mt-1 text-sm text-muted-foreground"> Only the team owner can delete this team. </p> <button className="mt-3 btn btn-destructive">Delete Team</button> </section> )} </div> );}Team members list with permissions
import { loadTeamWorkspace } from '~/home/[account]/_lib/server/team-account-workspace.loader';import { getSupabaseServerClient } from '@kit/supabase/server-client';export default async function TeamMembersPage() { const { account } = await loadTeamWorkspace(); const client = getSupabaseServerClient(); const canManageMembers = account.permissions.includes('members.manage'); const canRemoveMembers = account.permissions.includes('members.remove'); const canInviteMembers = account.permissions.includes('members.invite'); const { data: members } = await client .from('accounts_memberships') .select(` user_id, role, created_at, users:user_id ( email, user_metadata ) `) .eq('account_id', account.id); return ( <div> <header className="flex items-center justify-between"> <h1>Team Members</h1> {canInviteMembers && ( <a href={`/home/${account.slug}/settings/members/invite`}> Invite Member </a> )} </header> <table className="w-full"> <thead> <tr> <th>Member</th> <th>Role</th> <th>Joined</th> {(canManageMembers || canRemoveMembers) && <th>Actions</th>} </tr> </thead> <tbody> {members?.map((member) => ( <tr key={member.user_id}> <td>{member.users?.email}</td> <td>{member.role}</td> <td>{new Date(member.created_at).toLocaleDateString()}</td> {(canManageMembers || canRemoveMembers) && ( <td> {canManageMembers && member.user_id !== account.primary_owner_user_id && ( <button>Change Role</button> )} {canRemoveMembers && member.user_id !== account.primary_owner_user_id && ( <button>Remove</button> )} </td> )} </tr> ))} </tbody> </table> </div> );}Client-side permission hook
'use client';import { useTeamAccountWorkspace } from '@kit/team-accounts/hooks/use-team-account-workspace';export function useTeamPermissions() { const { account } = useTeamAccountWorkspace(); return { canManageSettings: account.permissions.includes('settings.manage'), canManageBilling: account.permissions.includes('billing.manage'), canInviteMembers: account.permissions.includes('members.invite'), canRemoveMembers: account.permissions.includes('members.remove'), canManageMembers: account.permissions.includes('members.manage'), isOwner: account.role === 'owner', isAdmin: account.role === 'admin' || account.role === 'owner', role: account.role, roleLevel: account.role_hierarchy_level, };}// Usagefunction TeamActions() { const permissions = useTeamPermissions(); return ( <div className="flex gap-2"> {permissions.canInviteMembers && ( <button>Invite Member</button> )} {permissions.canManageSettings && ( <button>Settings</button> )} {permissions.canManageBilling && ( <button>Billing</button> )} </div> );}Subscription-gated features
'use client';import { useTeamAccountWorkspace } from '@kit/team-accounts/hooks/use-team-account-workspace';export function PremiumFeature({ children }: { children: React.ReactNode }) { const { account } = useTeamAccountWorkspace(); const hasActiveSubscription = account.subscription_status === 'active' || account.subscription_status === 'trialing'; if (!hasActiveSubscription) { return ( <div className="rounded-lg border-2 border-dashed p-6 text-center"> <h3 className="font-medium">Premium Feature</h3> <p className="mt-1 text-sm text-muted-foreground"> Upgrade to access this feature </p> <a href={`/home/${account.slug}/settings/billing`} className="mt-3 inline-block btn btn-primary" > Upgrade Plan </a> </div> ); } return <>{children}</>;}Related documentation
- User Workspace API - Personal account context
- Team Account API - Team operations
- Authentication API - User authentication
- Per-seat Billing - Team-based pricing