Multi-Tenancy
Ever Gauzy is built as a multi-tenant platform where every piece of data is scoped to a specific tenant. This ensures complete data isolation between different organizations using the platform.
Tenant Modelโ
What is a Tenant?โ
A tenant represents a top-level organizational boundary โ typically a company or business entity that uses the Gauzy platform. Each tenant has:
- Its own users, employees, and organizations
- Isolated data (invoices, time logs, expenses, etc.)
- Independent configuration and settings
- Separate roles and permissions
Tenant Hierarchyโ
Tenant (Company)
โโโ Organization A (Branch / Division)
โ โโโ Department 1
โ โ โโโ Team Alpha
โ โ โโโ Team Beta
โ โโโ Department 2
โ โโโ Projects
โ โโโ Project X
โ โโโ Project Y
โโโ Organization B
โ โโโ ...
โโโ Users
โโโ Super Admin (tenant-wide)
โโโ Admin (per organization)
โโโ Employee (per organization)
How Tenant Isolation Worksโ
Entity Base Classesโ
All tenant-scoped entities extend TenantBaseEntity or TenantOrganizationBaseEntity:
// Tenant-scoped entity
export abstract class TenantBaseEntity extends BaseEntity {
@MultiORMManyToOne(() => Tenant, { nullable: false })
tenant: ITenant;
@MultiORMColumn({ relationId: true })
tenantId: string;
}
// Tenant + Organization scoped entity
export abstract class TenantOrganizationBaseEntity extends TenantBaseEntity {
@MultiORMManyToOne(() => Organization, { nullable: true })
organization?: IOrganization;
@MultiORMColumn({ relationId: true, nullable: true })
organizationId?: string;
}
Automatic Tenant Filteringโ
The TenantAwareCrudService base class automatically injects the current tenant's ID into all queries:
// In TenantAwareCrudService
async findAll(options?: FindManyOptions<T>): Promise<IPagination<T>> {
const tenantId = RequestContext.currentTenantId();
// Automatically adds WHERE tenant_id = :tenantId
return super.findAll({
...options,
where: {
...options?.where,
tenantId,
} as any,
});
}
This means:
- SELECT queries only return data for the current tenant
- INSERT operations automatically set
tenantId - UPDATE and DELETE operations are scoped to the current tenant
- No tenant data leaks are possible through the service layer
Request Contextโ
The tenant is resolved from the JWT token on every request:
JWT Token โ Auth Guard โ Tenant Resolve โ RequestContext.currentTenantId()
The RequestContext makes the tenant ID available throughout the request lifecycle:
const tenantId = RequestContext.currentTenantId();
Tenant Lifecycleโ
1. Tenant Creationโ
When a user registers publicly, a new tenant is created during onboarding:
// TenantService.onboardTenant()
async onboardTenant(user: IUser, input: TenantInput): Promise<ITenant> {
// Create tenant
const tenant = await this.tenantRepository.save({
name: input.name,
id: uuidv4(),
});
// Create default roles for the tenant
await this.roleService.createBulk(tenant.id);
// Assign SUPER_ADMIN role to the creating user
await this.userRepository.update(user.id, {
tenantId: tenant.id,
roleId: await this.findSuperAdminRole(tenant.id),
});
return tenant;
}