--- status: "published" label: "Team Workspace API" order: 4 title: "Team Workspace API | Next.js Supabase SaaS Kit" description: "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. {% sequence title="Team Workspace API Reference" description="Access team workspace data in layouts and components" %} [loadTeamWorkspace (Server)](#loadteamworkspace-server) [useTeamAccountWorkspace (Client)](#useteamaccountworkspace-client) [Data structure](#data-structure) [Usage patterns](#usage-patterns) {% /sequence %} ## loadTeamWorkspace (Server) Loads the team workspace data for the specified team account. Use this in Server Components within the `/home/[account]` route group. ```tsx 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 (

{data.account.name}

Your role: {data.account.role}

); } ``` ### Function signature ```tsx async function loadTeamWorkspace(): Promise ``` ### How it works The loader reads the `account` parameter from the URL (the team slug) and fetches: 1. Team account details from the database 2. Current user's role and permissions in this team 3. 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. ```tsx // Both calls use the same cached data const layout = await loadTeamWorkspace(); // First call: hits database const page = await loadTeamWorkspace(); // Second call: returns cached data ``` {% callout title="Performance consideration" %} While 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. {% /callout %} --- ## useTeamAccountWorkspace (Client) Access the team workspace data in client components using the `useTeamAccountWorkspace` hook. The data is provided through React Context from the layout. ```tsx 'use client'; import { useTeamAccountWorkspace } from '@kit/team-accounts/hooks/use-team-account-workspace'; export function TeamHeader() { const { account, user, accounts } = useTeamAccountWorkspace(); return (
{account.picture_url && ( {account.name} )}

{account.name}

{account.role} ยท {account.subscription_status || 'Free'}

); } ``` {% callout type="warning" title="Context requirement" %} 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. {% /callout %} --- ## Data structure ### TeamWorkspaceData ```tsx 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: ```tsx const { account } = useTeamAccountWorkspace(); // Check if user can manage someone with role_level 2 const canManage = account.role_hierarchy_level < 2; ``` ### account.permissions An array of permission strings the user has in this team: ```tsx [ '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 ```tsx '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}; } // Usage function TeamSettingsPage() { return (

Team Settings

You don't have permission to manage settings.

} >
); } ``` ### Team dashboard with role checks ```tsx 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 (

{account.name}

{account.subscription_status === 'active' ? 'Pro Plan' : 'Free Plan'}

{isAdmin && ( Team Settings )}

Recent Projects

{isOwner && (

Danger Zone

Only the team owner can delete this team.

)}
); } ``` ### Team members list with permissions ```tsx 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 (

Team Members

{canInviteMembers && ( Invite Member )}
{(canManageMembers || canRemoveMembers) && } {members?.map((member) => ( {(canManageMembers || canRemoveMembers) && ( )} ))}
Member Role JoinedActions
{member.users?.email} {member.role} {new Date(member.created_at).toLocaleDateString()} {canManageMembers && member.user_id !== account.primary_owner_user_id && ( )} {canRemoveMembers && member.user_id !== account.primary_owner_user_id && ( )}
); } ``` ### Client-side permission hook ```tsx '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, }; } // Usage function TeamActions() { const permissions = useTeamPermissions(); return (
{permissions.canInviteMembers && ( )} {permissions.canManageSettings && ( )} {permissions.canManageBilling && ( )}
); } ``` ### Subscription-gated features ```tsx '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 (

Premium Feature

Upgrade to access this feature

Upgrade Plan
); } return <>{children}; } ``` ## Related documentation - [User Workspace API](/docs/next-supabase-turbo/api/user-workspace-api) - Personal account context - [Team Account API](/docs/next-supabase-turbo/api/team-account-api) - Team operations - [Authentication API](/docs/next-supabase-turbo/api/authentication-api) - User authentication - [Per-seat Billing](/docs/next-supabase-turbo/billing/per-seat-billing) - Team-based pricing