Sub-Organizations
Introduction
When selecting Tenant as the organization type, the nubos:init command asks which sub-structure to scaffold within each tenant:
| Sub-structure | What gets generated |
|---|---|
None | Flat tenant, no sub-organizations |
Teams | Team model and actions, scoped to the current tenant |
Workspaces | Workspace model and actions, scoped to the current tenant |
Workspaces + Teams | Both Workspace and Team models with a Workspace > Team hierarchy, all scoped to the current tenant |
Modifications Applied
When a sub-organization (Team, Workspace) is placed under a tenant, four modifications are applied to the generated code:
1. Tenant Foreign Key
A tenant_id foreign key is added to the sub-organization's migration:
$table->foreignUuid('tenant_id')->constrained()->cascadeOnDelete();2. TenantScope Trait
The TenantScope trait is added to the sub-organization model. This global scope automatically filters all queries by the current tenant's ID and auto-sets tenant_id on new records. Without this trait, sub-organizations from all tenants would be visible.
3. TenantIdentification Middleware
The sub-organization's route file is modified to include TenantIdentification middleware before the organization middleware. This ensures the tenant is resolved from the subdomain before the sub-organization is resolved.
4. Scoped Slug Uniqueness
The slug uniqueness constraint changes from a global unique index to a composite unique index scoped to the tenant:
// Without tenant:
$table->string('slug')->unique();
// Under tenant:
$table->string('slug');
$table->unique(['tenant_id', 'slug']);This allows different tenants to have sub-organizations with the same slug.
Migration Numbering
Sub-organization migrations are renumbered to run after tenant migrations. The nubos:init command remaps migration numbers:
| Original | Remapped |
|---|---|
110000 | 120003 |
110001 | 120004 |
110002 | 120006 |
110003 | 120007 |
110004 | 120005 |
110005 | 120008 |
This ensures tenant tables (120000-120002) are created before sub-organization tables that reference them.
Configuration Examples
Tenant with Teams:
return [
'organization_type' => 'tenant',
'organization_model' => \App\Models\Tenant::class,
'database_strategy' => 'single',
'sub_organization' => 'team',
'has_sub_teams' => true,
'sub_team_model' => \App\Models\Team::class,
];Tenant with Workspaces:
return [
'organization_type' => 'tenant',
'organization_model' => \App\Models\Tenant::class,
'database_strategy' => 'single',
'sub_organization' => 'workspace',
'has_sub_teams' => true,
'sub_organization_model' => \App\Models\Workspace::class,
];Tenant with Workspaces + Teams:
return [
'organization_type' => 'tenant',
'organization_model' => \App\Models\Tenant::class,
'database_strategy' => 'single',
'sub_organization' => 'workspace-teams',
'has_sub_teams' => true,
'sub_organization_model' => \App\Models\Workspace::class,
'sub_team_model' => \App\Models\Team::class,
];All three sub-structure options work with both single and multi database strategies.
