Organization Concept

Understanding organizations and multi-tenancy model.

The Next.js Prisma kit uses a dual-account model: personal accounts for individual users and organizations for team collaboration. Both account types share the same data model and route structure.

The kit's dual-account model provides both personal accounts (single-user, auto-created at registration) and organizations (multi-user, manually created). Both use the same accounts table and share route structures - context determines which account is active, not the URL. This enables building apps that serve individual users and teams simultaneously.

The dual-account model is the kit's multi-tenancy architecture where users have a personal account (1:1) and can belong to multiple organizations (many:many via memberships).

  • Use personal accounts when: building B2C apps, users don't share data, or you need individual workspaces.
  • Use organizations when: building B2B SaaS, teams need shared workspaces, or you require role-based collaboration.
  • If unsure: use hybrid mode (default). Users get both account types and can choose what fits their workflow.

Account Model Diagram

┌─────────────────────────────────────────────────────────────────┐
│ accounts table │
├─────────────────────────────────────────────────────────────────┤
│ Personal Account │ Organization │
│ ───────────────── │ ──────────── │
│ id = user.id │ id = generated UUID │
│ is_personal_account: true │ is_personal_account: false │
│ No memberships table │ Has memberships (user_id, role) │
│ Single owner │ Multiple members with roles │
└─────────────────────────────────────────────────────────────────┘
User Registration Flow:
┌──────┐ creates ┌──────────────────┐
│ User │ ───────────────►│ Personal Account │ (automatic)
└──────┘ └──────────────────┘
│ can create/join
┌──────────────┐ membership ┌──────────────┐
│ Organization │◄────────────►│ Organization │ (manual)
└──────────────┘ └──────────────┘

Personal Accounts vs Organizations

AspectPersonal AccountOrganization
OwnershipTied to one userIndependent entity
MembersSingle user onlyMultiple members with roles
CreationAutomatic on registrationUser creates manually
BillingPer-user subscriptionsPer-organization subscriptions
Use caseB2C, individual toolsB2B, team collaboration

Personal Accounts are created automatically when a user registers. The personal account ID equals the user's auth ID (auth.users.id = accounts.id). Personal accounts cannot have additional members.

Organizations are created by users and exist independently. Multiple users join as members with assigned roles (owner, admin, member). Each organization has its own settings, billing, and data.

How Context Switching Works

Users switch between accounts using the sidebar dropdown. When switching:

  1. The active account ID is stored in application state
  2. All data queries filter by the active account
  3. The UI reflects the current account's settings and permissions
  4. Routes remain the same (/dashboard, /settings) - only the context changes
// Access current organization in a team page
import { useTeamAccountWorkspace } from '@kit/team-accounts/hooks/use-team-account-workspace';
function TeamComponent() {
const { account, user, accounts } = useTeamAccountWorkspace();
// account = current organization
// user = authenticated user
// accounts = all organizations user belongs to
}

Account Modes

Configure which account types are available:

ModePersonal AccountsOrganizationsBest For
hybrid (default)B2B2C, flexibility
personal-onlyB2C, individual tools
organizations-onlyB2B, team-only apps

See Account Modes for configuration details.

Data Association

All user data associates with an account_id foreign key:

-- Example: projects table
CREATE TABLE projects (
id UUID PRIMARY KEY,
account_id UUID REFERENCES accounts(id), -- Personal or org
name TEXT NOT NULL
);
-- RLS policy ensures users only see their account's data
CREATE POLICY "Users can view their account's projects"
ON projects FOR SELECT
USING (account_id IN (
SELECT account_id FROM memberships WHERE user_id = auth.uid()
));

This unified model means your application code works identically for personal and organization contexts - RLS handles the access control.

Common Pitfalls

  • Forgetting RLS on new tables: When adding tables with account_id, always create RLS policies. Without them, data is accessible to all users.
  • Using wrong workspace hook: Use useTeamAccountWorkspace() for organization pages, useUserWorkspace() for personal account pages. Mixing them causes undefined errors.
  • Assuming URL distinguishes account type: Both account types share /dashboard, /settings, etc. The active account is in application state, not the URL.
  • Querying without account filter: Always include .eq('account_id', accountId) in queries. Relying on RLS alone without explicit filters can cause performance issues.

Next: Dashboard →