Skip to content

Tenant Bootstrap

Guide to creating and setting up new tenants in Lager Guru.

Overview

Tenant bootstrap is the process of creating a new tenant organization and setting up its initial configuration. This is typically done by a super admin.

Bootstrap Flow

┌─────────────────┐
│  Super Admin    │
│  Creates Tenant │
└────────┬────────┘


┌─────────────────┐
│  Create Tenant  │
│  Record         │
└────────┬────────┘


┌─────────────────┐
│  Assign Admin   │
│  User           │
└────────┬────────┘


┌─────────────────┐
│  Initialize     │
│  Settings       │
└────────┬────────┘


┌─────────────────┐
│  Tenant Ready   │
└─────────────────┘

Step-by-Step Process

1. Create Tenant Record

typescript
import { supabase } from '@/integrations/supabase/client';
import { createAdminClient } from '@/lib/auth-enterprise/...'; // Use service role

async function createTenant(name: string, companyData?: any) {
  const adminClient = createAdminClient();
  
  const { data, error } = await adminClient
    .from('tenants')
    .insert({
      name,
      company: companyData || {},
      active: true,
    })
    .select()
    .single();
  
  if (error) throw error;
  return data;
}

Required Fields:

  • name: Organization name (required)

Optional Fields:

  • company: JSONB object with company metadata
  • active: Defaults to true

2. Assign Admin User

After creating the tenant, assign an admin user:

typescript
async function assignTenantAdmin(tenantId: string, userId: string) {
  const adminClient = createAdminClient();
  
  const { data, error } = await adminClient
    .from('tenant_users')
    .insert({
      tenant_id: tenantId,
      user_id: userId,
      role: 'tenant_admin',
    })
    .select()
    .single();
  
  if (error) throw error;
  return data;
}

Note: The user must already exist in the profiles table (created via Supabase Auth).

3. Initialize System Settings

Create default system settings for the tenant:

typescript
async function initializeTenantSettings(tenantId: string) {
  const adminClient = createAdminClient();
  
  const defaultSettings = {
    company: {
      name: '',
      address: '',
      logoUrl: '',
      adminContact: '',
    },
    branding: {
      primaryColor: '#3b82f6',
      accentColor: '#10b981',
      theme: 'dark',
    },
    // ... other default settings
  };
  
  const { data, error } = await adminClient
    .from('system_settings')
    .insert({
      tenant_id: tenantId,
      settings: defaultSettings,
    })
    .select()
    .single();
  
  if (error) throw error;
  return data;
}

4. Initialize Auto-Assignment Settings

typescript
async function initializeAutoAssignSettings(tenantId: string) {
  const adminClient = createAdminClient();
  
  const { data, error } = await adminClient
    .from('auto_assign_settings')
    .insert({
      tenant_id: tenantId,
      mode: 'suggest', // or 'off', 'auto'
    })
    .select()
    .single();
  
  if (error) throw error;
  return data;
}

Complete Bootstrap Function

Here's a complete function that does all steps:

typescript
interface BootstrapTenantOptions {
  name: string;
  adminUserId: string;
  companyData?: any;
  autoAssignMode?: 'off' | 'suggest' | 'auto';
}

async function bootstrapTenant(options: BootstrapTenantOptions) {
  const adminClient = createAdminClient();
  
  try {
    // Step 1: Create tenant
    const { data: tenant, error: tenantError } = await adminClient
      .from('tenants')
      .insert({
        name: options.name,
        company: options.companyData || {},
        active: true,
      })
      .select()
      .single();
    
    if (tenantError) throw tenantError;
    
    // Step 2: Assign admin
    const { error: adminError } = await adminClient
      .from('tenant_users')
      .insert({
        tenant_id: tenant.id,
        user_id: options.adminUserId,
        role: 'tenant_admin',
      });
    
    if (adminError) throw adminError;
    
    // Step 3: Initialize settings
    const defaultSettings = {
      company: { name: options.name, ...options.companyData },
      branding: { primaryColor: '#3b82f6', accentColor: '#10b981' },
      // ... other defaults
    };
    
    await adminClient
      .from('system_settings')
      .insert({
        tenant_id: tenant.id,
        settings: defaultSettings,
      });
    
    // Step 4: Initialize auto-assign
    await adminClient
      .from('auto_assign_settings')
      .insert({
        tenant_id: tenant.id,
        mode: options.autoAssignMode || 'suggest',
      });
    
    return { tenant, success: true };
  } catch (error) {
    console.error('Tenant bootstrap failed:', error);
    throw error;
  }
}

Using the Bootstrap Script

Lager Guru includes a bootstrap script for super admins:

bash
# Bootstrap a new tenant
npm run bootstrap:tenant

Or use the function directly:

typescript
import { bootstrapTenant } from '@/lib/tenants';

const result = await bootstrapTenant({
  name: 'Acme Corporation',
  adminUserId: 'user-uuid-here',
  companyData: {
    address: '123 Main St',
    contact: 'admin@acme.com',
  },
  autoAssignMode: 'suggest',
});

UI Flow (Super Admin)

  1. Navigate to Tenant Management

    • Super admin goes to tenant management page
    • Clicks "Create New Tenant"
  2. Fill Tenant Details

    • Enter tenant name
    • Optionally enter company information
    • Select admin user (from existing users)
  3. Submit

    • System creates tenant
    • Assigns admin user
    • Initializes all settings
  4. Confirmation

    • Tenant is active and ready
    • Admin user can now log in

Validation Rules

Tenant Name

  • Required
  • Must be unique (enforced by application, not database)
  • Recommended: Company/organization name

Admin User

  • Must exist in profiles table
  • Should not already be assigned to another tenant (unless multi-tenant users are allowed)
  • Will receive tenant_admin role

Company Data

  • Optional JSONB field
  • Can include:
    • address: Physical address
    • contact: Contact email/phone
    • taxId: Tax identification number
    • website: Company website
    • Any other custom fields

Post-Bootstrap Steps

After tenant is created:

  1. Admin Login: Admin user logs in and sees their tenant
  2. Initial Configuration: Admin configures:
    • System settings
    • Zones
    • User roles
    • Integrations
  3. User Onboarding: Admin invites/adds users to tenant
  4. Data Seeding: Optionally seed initial data (zones, inventory, etc.)

Error Handling

Duplicate Tenant Name

typescript
// Check before creating
const { data: existing } = await supabase
  .from('tenants')
  .select('id')
  .eq('name', name)
  .maybeSingle();

if (existing) {
  throw new Error('Tenant with this name already exists');
}

Invalid Admin User

typescript
// Verify user exists
const { data: user } = await supabase
  .from('profiles')
  .select('id')
  .eq('id', adminUserId)
  .single();

if (!user) {
  throw new Error('Admin user does not exist');
}

Transaction Safety

For production, wrap in a transaction:

typescript
// Note: Supabase doesn't support transactions in client
// Use Edge Function or service role with transaction
BEGIN;
  INSERT INTO tenants ...;
  INSERT INTO tenant_users ...;
  INSERT INTO system_settings ...;
COMMIT;

Released under Commercial License