Site

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

┌─────────────────────────────────────────────────────────────────┐
│                 APP USER (private.app_users)                   │
│      resolved from provider + issuer + provider subject        │
└─────────────────────────────────────────────────────────────────┘
                                 │
                                 │ 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         (provider-scoped 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 verified user gets a personal Account through the explicit server-side application-identity bootstrap. 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 the provider-neutral subscriptions.provider field through checkAccountAccess's details.isProviderManaged signal 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 configured provider's 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 configured provider's customer 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/change-plan, /api/billing/subscription-control, and /api/billing/end-trial. Portal, plan-change, lifecycle and trial mutations retain strict rate limiting and billing step-up; the read-only end-trial status uses the standard limit without another freshness challenge. These management routes pass the wrapper's active appUserId to actor-scoped Prisma helpers, while session and MFA checks retain the provider coordinate. Account membership and billing-manager checks apply in the same transaction context. /api/billing/subscribe-free stays owner-only across all modes, and /api/org/schedule-deletion remains owner-only.

Member checkout and licence checkout pass a minimal trusted actor, {'{ appUserId, email }'}, rather than an Auth SDK user. The application identity scopes memberships, deferred referral/affiliate attribution, durable attempts and signed payment context. The email comes from the revalidated principal, never request data. The common SQL and Prisma contracts use the same identity model for all four Database/Auth compositions.

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. After provider verification, the callback invokes the canonical application bootstrap, which atomically creates or revalidates the profile, personal Account and owner membership before activation.
  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 only internal purchase facts and the workspace Account target to /api/billing/checkout. The server resolves PAYMENTS_PROVIDER and its catalogue binding, and returns 400 WORKSPACE_REQUIRED if a B2B subscription targets a personal Account. The cookie is cleared before the configured-provider redirect.
  5. Configured-provider payment — the user completes payment on Stripe, Lemon Squeezy, or Paddle according to the installation; the UI never offers that choice.
  6. Webhook processing — the signed provider event is journaled, normalized, and applied to the provider-neutral subscription contract, superseding the current internal free row on the same Account. Paid credits are granted only from normalized provider payment proof, through add_credits, with a semantic key scoped to the provider subscription and billing period.
  7. Success page — the provider returns through the local status exchange. An opaque bearer is rotated into a short HttpOnly cookie before the clean /checkout/success URL renders. Only a durable completed attempt may route to onboarding or the correct dashboard; no provider session ID appears in the browser URL or grants access.
  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.