The boilerplate supports both B2C (Business-to-Consumer) and B2B (Business-to-Business) models with a flexible account-centric architecture.

B2C vs B2B Business Models

Choose your business model by setting the businessModel property in config/app.ts to either 'b2c' or 'b2b'. This single setting controls account creation behavior, team features visibility, the billing flow, and the sidebar navigation.

Comparison Table

Feature B2C Mode B2B Mode
Account Type Personal accounts only Personal + Workspace accounts
Account Creation Personal account auto-created on signup (1 user = 1 account) Personal account + workspace both auto-bootstrapped at signup via ensureWorkspaceForUser (idempotent); invitations bring teammates in afterward
Team Members Single user per account Multiple users per workspace
Billing Per-user billing Per-workspace billing (shared credits)
Roles System roles (owner / admin / member) still seeded; only multi-member team management is unused Owner, Admin, Member, Custom roles — actively used for team management
Invitations Not applicable Email invitations with role assignment
Account Switcher Hidden Visible (switch between workspaces)
Use Case Individual users, solo apps Teams, agencies, enterprises

Architecture Diagram

┌─────────────────────────────────────────────────────────────────┐
│                        USER (auth.users)                        │
│                    email, created_at, etc.                      │
└─────────────────────────────────────────────────────────────────┘
                                 │
                                 │ can have multiple
                                 ▼
┌─────────────────────────────────────────────────────────────────┐
│              MEMBERSHIPS (user_id, account_id, role_slug)       │
│                                                                 │
│   ┌─────────────┐    ┌─────────────┐    ┌─────────────┐        │
│   │ User A      │    │ User A      │    │ User B      │        │
│   │ Account 1   │    │ Account 2   │    │ Account 2   │        │
│   │ role: owner │    │ role: member│    │ role: admin │        │
│   └─────────────┘    └─────────────┘    └─────────────┘        │
└─────────────────────────────────────────────────────────────────┘
                                 │
                                 │ belongs to
                                 ▼
┌─────────────────────────────────────────────────────────────────┐
│                 ACCOUNT (personal | workspace)                  │
│                                                                 │
│   ├── credits_balance      (shared pool for all members)        │
│   ├── subscription         (Stripe subscription)                │
│   ├── chat_sessions        (AI conversations)                   │
│   ├── ai_requests          (usage logs)                         │
│   ├── api_keys             (B2B API access)                     │
│   └── settings             (account preferences)                │
└─────────────────────────────────────────────────────────────────┘
Key Principle: Everything is attached to an Account, never directly to a User. This enables seamless B2B features like shared credits, team billing, and workspace switching.

B2C Mode Details

In B2C mode, each user gets their own personal account automatically created on signup via a database trigger. The personal account holds the user's subscription, credits, and chat history. There are no workspaces, invitations, or team features. The account switcher is hidden in the UI, and billing is tied to the individual user.

B2B Mode Details

In B2B mode, users can create workspaces and invite team members. Each workspace is a separate account with its own subscription, credits, and data. Users can belong to multiple workspaces and switch between them using the account switcher. Workspace owners can invite members via email, assign roles (owner, admin, member, or custom), and manage billing centrally for the entire team.

Workspace targeting is mandatory: in B2B mode, subscription checkout targets a workspace account. Workspaces are auto-bootstrapped via ensureWorkspaceForUser() (idempotent) at five sites — the magic-link callback, the OAuth callback, POST /api/auth/verify-otp, the /checkout Server Component, and the free-plan path through /api/billing/subscribe-free (via subscribeToFreePlan in core/billing/free-plan.ts). /onboarding/workspace remains as a legacy manual-creation fallback only. The created membership writes both role and role_slug as 'owner', so the Manage organization entry and all permission helpers resolve immediately.
Upgrade CTAs are scoped to /org-dashboard: in B2B mode, the personal /private-dashboard hides the upgrade button (sidebar, subscription card, Quick Actions). Workspace billing lives on /org-dashboard/billing via OrgBillingActions. The signal flows from subscriptions.stripe_subscription_id through checkAccountAccess's new details.stripeSubscriptionId field combined with appConfig.businessModel.

Organization Dashboard

The Organization Dashboard (/org-dashboard) is the central management interface for workspace owners and admins. It provides tools for managing members, billing, settings, and analytics.

Access Control: Only users with owner or admin roles can access the org-dashboard. Members are automatically redirected.
Dashboard Structure
org-dashboard/
├── admin/            # Workspace admin overview
├── members/          # Team member management
│   ├── [memberId]/   # Individual member details
│   └── roles/        # Role overview & permissions
├── billing/          # Subscription & credits
├── api-keys/         # B2B API keys
├── settings/         # Workspace configuration
└── analytics/        # Usage analytics
Members Management

The Members page displays all workspace members with their roles and AI usage statistics.

Billing Management

The Billing page shows the current subscription, credits balance, and payment history.

┌─────────────────────────────────────────────────────────────────┐
│                      BILLING DASHBOARD                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌──────────────────┐  ┌──────────────────┐                    │
│  │   Current Plan   │  │  Credits Balance │                    │
│  │   ────────────   │  │  ──────────────  │                    │
│  │   Pro Plan       │  │   2,498 credits  │                    │
│  │   $29/month      │  │   ───────────    │                    │
│  │   [Manage]       │  │   [Buy More]     │                    │
│  └──────────────────┘  └──────────────────┘                    │
│                                                                 │
│  Credit Transactions                                            │
│  ─────────────────────────────────────────────────              │
│  │ Date       │ Type        │ Amount  │ Balance │              │
│  │ 2024-01-15 │ AI Request  │ -1      │ 2,498   │              │
│  │ 2024-01-15 │ AI Request  │ -1      │ 2,499   │              │
│  │ 2024-01-01 │ Subscription│ +2,500  │ 2,500   │              │
│  ─────────────────────────────────────────────────              │
│                                                                 │
│  Recent Payments                                                │
│  ─────────────────────────────────────────────────              │
│  │ Date       │ Description     │ Amount │ Status  │           │
│  │ 2024-01-01 │ Pro Plan        │ $29.00 │ Paid    │           │
│  │ 2023-12-15 │ Credit Pack     │ $19.00 │ Paid    │           │
│  ─────────────────────────────────────────────────              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

The Billing page in the org dashboard displays the current subscription plan, credit balance, recent payments, and a link to the Stripe Customer Portal for managing payment methods and invoices. Admins and owners can access this page; regular members are restricted based on role permissions.

Workspace Settings

The Settings page allows workspace configuration and includes a danger zone for destructive actions.

Settings Page Structure:
─────────────────────────

1. General Settings
   ├── Workspace Name (editable)
   ├── Workspace Slug (URL identifier)
   └── Created Date (read-only)

2. Organization Info
   ├── Account ID
   ├── Account Type (workspace)
   └── Members Count

3. Danger Zone (owner only)
   ├── Schedule Deletion (30-day grace period)
   ├── Cancel Scheduled Deletion
   └── Warning: Notifies all members via email
Access Control Implementation

Permission checks are performed using the checkPermission(accountId, permission) function from lib/permissions/check.ts. This function verifies the current user's membership and role in the specified account, then checks whether that role has the required permission. UI components conditionally render based on the user's role (e.g., the billing tab is only shown to owners and admins in B2C/hybrid, or owners only in B2B).

Billing-Manager Role (B2B Owner-Only)

Every billing route checks hasRole(membership, getBillingManagerRoles()) from lib/permissions/check.ts — a single source of truth for who can change financial state. The helper returns:

  • ['owner'] when appConfig.businessModel === 'b2b' — the workspace owner is the sole accountable billing party. Admins can run the org but cannot trigger checkout, change plans, open the Stripe portal, or end a trial.
  • ['owner', 'admin'] in B2C / hybrid mode — admins keep their billing capability.

Routes wired to this helper: /api/billing/checkout, /api/billing/license-checkout, /api/billing/portal, /api/billing/end-trial. /api/billing/subscribe-free stays owner-only across all modes (free-plan creation is identity-shaping). /api/org/schedule-deletion is owner-only at the API regardless of mode.

When adding a new billing route, never inline hasRole(membership, ['owner', 'admin']) — import getBillingManagerRoles() so future B2B-only tightening propagates automatically.

Team Invitations

In B2B mode, workspace owners and admins invite teammates by email; each invite creates a single-use token with a 7-day expiry, then drops a membership when accepted. The full flow (token lifecycle, accept/resend API, email template builder, and security notes) lives in Team Invitations.

B2B Subscription Flow (auto-bootstrap workspace)

B2B requires the subscription, credits, invitations, and team data to attach to a workspace account. The app now creates that workspace automatically during the auth/checkout bootstrap when the user does not already have one. The selected plan is carried across the auth round-trip by the pending-checkout cookie.

  1. Plan selection — User clicks a plan on /pricing. A short-lived httpOnly bsk_pending_checkout cookie (30min, Zod-validated payload) carries the choice across the auth round-trip. sessionStorage mirrors the cookie as a same-tab fast path.
  2. Sign up / log in — Magic link, OTP code, or OAuth. The handle_new_user_account trigger on auth.users INSERT (signup) creates the personal account.
  3. Workspace bootstrap — In B2B mode, ensureWorkspaceForUser() creates a workspace account and owner membership when the user has none. /onboarding/workspace remains a fallback if bootstrap fails.
  4. Checkout/checkout reads the pending checkout cookie. The client POSTs to /api/billing/checkout with the workspace account target. The route validates the Stripe price against pricingConfig and returns 400 WORKSPACE_REQUIRED if a B2B subscription request targets a personal account. The cookie is cleared via Server Action right before the Stripe redirect.
  5. Stripe payment — user completes payment on Stripe.
  6. Webhook processingcustomer.subscription.created upserts the row, cancels any legacy free row (stripe_subscription_id IS NULL) on the same account, and grants the plan's full credits via the add_credits RPC. Idempotent via UNIQUE payments.stripe_session_id = sub_init_${sub.id}.
  7. Success page — Stripe redirects to /checkout/success?session_id=cs_…. The Server Component reads profile.onboarding_completed + runs isWorkspaceManager in parallel: if the user hasn't yet onboarded, dashboardPath = /onboarding; otherwise dashboard is chosen by the workspace-manager rule (/org-dashboard for owners/admins, else /private-dashboard).
  8. Profile onboarding (when needed)OnboardingForm collects first/last name and optional phone, then routes to the appropriate dashboard. Workspace renaming can be done later from /org-dashboard/settings.
Single onboarding gate

The decision "send the buyer to /onboarding or directly to a dashboard?" lives in exactly one place: app/[locale]/(auth)/checkout/success/page.tsx (Server Component). Re-introducing the gate in middleware, in a Client Component, or in another Route Handler is a regression (anti-pattern A13). The middleware's onboarding gate explicitly exempts /checkout and /checkout/success, and the /login gate honors redirectTo=/checkout* before falling through to the onboarding redirect.

B2B workspace bootstrap — five call sites

The workspace requirement is handled by idempotent server-side bootstrap plus API enforcement, so a B2B subscription never attaches to a personal account by accident:

  1. callback/route.ts bootstraps after magic-link verification.
  2. callback/route.ts bootstraps after OAuth session exchange.
  3. POST /api/auth/verify-otp bootstraps after OTP code verification.
  4. /checkout/page.tsx bootstraps as defense-in-depth before the checkout client runs.
  5. POST /api/billing/subscribe-free bootstraps as defense-in-depth for free plans.

POST /api/billing/checkout still returns 400 WORKSPACE_REQUIRED if a B2B subscription request targets a personal account.

This keeps the rule simple for new developers: B2C bills the personal account; B2B bills the workspace account.

Invited User Flow

When an invited user accepts an invitation:

  1. Click invitation link → Accept invitation page
  2. API-based acceptance via /api/invitations/accept
  3. Redirect to success page → Onboarding (if needed)
  4. Subscription check: If workspace has active subscription, redirect via the workspace-manager rule (isWorkspaceManager(user.id)) — /org-dashboard for owners/admins, else /private-dashboard (not pricing)
  5. Dashboard prioritizes workspace account for data display

Organization Member Limits

Organizations can have configurable member limits:

Feature Description
max_members Configurable per organization (null = unlimited)
Enforcement Limit checked on member invitation
Admin Update Can be updated via admin dashboard
Visual Indicator Warning shown when limit is reached

Roles & Permissions

The boilerplate ships a dynamic, database-driven roles system with three protected system roles (owner, admin, member), a 12-permission matrix, and custom roles managed via the admin dashboard. The full system-roles table, permission matrix, available permissions list, checkPermission() usage, and custom-roles API surface live in Roles & Permissions.