User Workspace API | Next.js Supabase SaaS Kit
Access personal workspace data in MakerKit layouts. Load user account information, subscription status, and account switcher data with the User Workspace API.
The User Workspace API provides personal account context for pages under /home/(user). It loads user data, subscription status, and all accounts the user belongs to, making this information available to both server and client components.
User Workspace API Reference
Access personal workspace data in layouts and components
loadUserWorkspace (Server)
Loads the personal workspace data for the authenticated user. Use this in Server Components within the /home/(user) route group.
import { loadUserWorkspace } from '~/home/(user)/_lib/server/load-user-workspace';export default async function PersonalDashboard() { const data = await loadUserWorkspace(); return ( <div> <h1>Welcome, {data.user.email}</h1> <p>Account: {data.workspace.name}</p> </div> );}Function signature
async function loadUserWorkspace(): Promise<UserWorkspaceData>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 loadUserWorkspace(); // First call: hits databaseconst page = await loadUserWorkspace(); // Second call: returns cached dataWhile calls are deduplicated within a request, the data is fetched on every navigation. If you only need a subset of the data (like subscription status), consider making a more targeted query.
useUserWorkspace (Client)
Access the workspace data in client components using the useUserWorkspace hook. The data is provided through React Context from the layout.
'use client';import { useUserWorkspace } from '@kit/accounts/hooks/use-user-workspace';export function ProfileCard() { const { workspace, user, accounts } = useUserWorkspace(); return ( <div className="rounded-lg border p-4"> <div className="flex items-center gap-3"> {workspace.picture_url && ( <img src={workspace.picture_url} alt={workspace.name ?? 'Profile'} className="h-10 w-10 rounded-full" /> )} <div> <p className="font-medium">{workspace.name}</p> <p className="text-sm text-muted-foreground">{user.email}</p> </div> </div> {workspace.subscription_status && ( <div className="mt-3"> <span className="text-xs uppercase tracking-wide text-muted-foreground"> Plan: {workspace.subscription_status} </span> </div> )} </div> );}The useUserWorkspace hook only works within the /home/(user) route group where the context provider is set up. Using it outside this layout will throw an error.
Data structure
UserWorkspaceData
import type { User } from '@supabase/supabase-js';interface UserWorkspaceData { workspace: { id: string | null; name: string | null; picture_url: string | null; public_data: Json | null; subscription_status: SubscriptionStatus | null; }; user: User; accounts: Array<{ id: string | null; name: string | null; picture_url: string | null; role: string | null; slug: string | null; }>;}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 |
accounts array
The accounts array contains all accounts the user belongs to, including:
- Their personal account
- Team accounts where they're a member
- The user's role in each account
This data powers the account switcher component.
Usage patterns
Personal dashboard page
import { loadUserWorkspace } from '~/home/(user)/_lib/server/load-user-workspace';export default async function DashboardPage() { const { workspace, user, accounts } = await loadUserWorkspace(); const hasActiveSubscription = workspace.subscription_status === 'active' || workspace.subscription_status === 'trialing'; return ( <div className="space-y-6"> <header> <h1 className="text-2xl font-bold">Dashboard</h1> <p className="text-muted-foreground"> Welcome back, {user.user_metadata.full_name || user.email} </p> </header> {!hasActiveSubscription && ( <div className="rounded-lg border border-yellow-200 bg-yellow-50 p-4"> <p>Upgrade to unlock premium features</p> <a href="/pricing" className="text-primary underline"> View plans </a> </div> )} <section> <h2 className="text-lg font-medium">Your teams</h2> <ul className="mt-2 space-y-2"> {accounts .filter((a) => a.slug !== null) .map((account) => ( <li key={account.id}> <a href={`/home/${account.slug}`} className="flex items-center gap-2 rounded-lg p-2 hover:bg-muted" > {account.picture_url && ( <img src={account.picture_url} alt="" className="h-8 w-8 rounded" /> )} <span>{account.name}</span> <span className="ml-auto text-xs text-muted-foreground"> {account.role} </span> </a> </li> ))} </ul> </section> </div> );}Account switcher component
'use client';import { useUserWorkspace } from '@kit/accounts/hooks/use-user-workspace';import { useRouter } from 'next/navigation';import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from '@kit/ui/select';export function AccountSwitcher() { const { workspace, accounts } = useUserWorkspace(); const router = useRouter(); const handleChange = (value: string) => { if (value === 'personal') { router.push('/home'); } else { router.push(`/home/${value}`); } }; return ( <Select defaultValue={workspace.id ?? 'personal'} onValueChange={handleChange} > <SelectTrigger className="w-[200px]"> <SelectValue /> </SelectTrigger> <SelectContent> <SelectItem value="personal"> Personal Account </SelectItem> {accounts .filter((a) => a.slug) .map((account) => ( <SelectItem key={account.id} value={account.slug!}> {account.name} </SelectItem> ))} </SelectContent> </Select> );}Feature gating with subscription status
'use client';import { useUserWorkspace } from '@kit/accounts/hooks/use-user-workspace';interface FeatureGateProps { children: React.ReactNode; fallback?: React.ReactNode; requiredStatus?: string[];}export function FeatureGate({ children, fallback, requiredStatus = ['active', 'trialing'],}: FeatureGateProps) { const { workspace } = useUserWorkspace(); const hasAccess = requiredStatus.includes( workspace.subscription_status ?? '' ); if (!hasAccess) { return fallback ?? null; } return <>{children}</>;}// Usagefunction PremiumFeature() { return ( <FeatureGate fallback={ <div className="text-center p-4"> <p>This feature requires a paid plan</p> <a href="/pricing">Upgrade now</a> </div> } > <ExpensiveComponent /> </FeatureGate> );}Combining with server data
import { loadUserWorkspace } from '~/home/(user)/_lib/server/load-user-workspace';import { getSupabaseServerClient } from '@kit/supabase/server-client';export default async function TasksPage() { const { workspace, user } = await loadUserWorkspace(); const client = getSupabaseServerClient(); // Fetch additional data using the workspace context const { data: tasks } = await client .from('tasks') .select('*') .eq('account_id', workspace.id) .eq('created_by', user.id) .order('created_at', { ascending: false }); return ( <div> <h1>My Tasks</h1> <TaskList tasks={tasks ?? []} /> </div> );}Related documentation
- Team Workspace API - Team account context
- Account API - Account operations
- Authentication API - User authentication