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 metadataactive: Defaults totrue
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:tenantOr 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)
Navigate to Tenant Management
- Super admin goes to tenant management page
- Clicks "Create New Tenant"
Fill Tenant Details
- Enter tenant name
- Optionally enter company information
- Select admin user (from existing users)
Submit
- System creates tenant
- Assigns admin user
- Initializes all settings
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
profilestable - Should not already be assigned to another tenant (unless multi-tenant users are allowed)
- Will receive
tenant_adminrole
Company Data
- Optional JSONB field
- Can include:
address: Physical addresscontact: Contact email/phonetaxId: Tax identification numberwebsite: Company website- Any other custom fields
Post-Bootstrap Steps
After tenant is created:
- Admin Login: Admin user logs in and sees their tenant
- Initial Configuration: Admin configures:
- System settings
- Zones
- User roles
- Integrations
- User Onboarding: Admin invites/adds users to tenant
- 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;Related Documentation
- Database Structure - Table schemas
- Service Role - Using service role for bootstrap
- Seed Strategy - Seeding initial data