Multi-Tenancy
Kinotic supports multi-tenant data isolation at the persistence layer. Data from different tenants is stored together but automatically filtered so each tenant only sees their own data.
Defining a Multi-Tenant Entity
Add @TenantId to a field and set the entity to MultiTenancyType.SHARED.
import { Entity, AutoGeneratedId, TenantId, MultiTenancyType } from '@kinotic-ai/persistence'
@Entity(MultiTenancyType.SHARED)
export class Person {
@AutoGeneratedId
id: string | null = null
@TenantId
tenantId: string = ''
firstName: string = ''
lastName: string = ''
}
The @TenantId field is populated automatically based on the authenticated user's tenant context. You do not need to set it manually when saving entities.
Where a user's tenant comes from
Declaring the entity is only half of it. A user's tenant is assigned when the user is created, and only when the owning application has tenantPerUser enabled — the setting that decides whether each user of the application is isolated or everyone shares one set of data.
Application tenantPerUser | Entity | Result |
|---|---|---|
false | MultiTenancyType.NONE | One shared dataset — every user sees every other user's rows |
true | MultiTenancyType.NONE | Still one shared dataset; the setting has nothing to act on |
false | SHARED + @TenantId | Users have no tenant, and writes fail with tenantId cannot be null or blank for shared multi tenancy |
true | SHARED + @TenantId | Each user isolated in their own tenant |
Choose it when the application is created — createApplicationIfNotExist takes it as an argument — and change it afterwards only from the portal, under Application → Settings → Tenant per user.
It applies to users created while it is enabled. Existing users are never backfilled, so enabling it on an application that already has users leaves those users without a tenant, and every write they make to a SHARED entity fails. Settle it before the application has users.
Tenants and services
A tenant comes from the authenticated participant, and only an application-scope participant carries one. A microservice running in a project's deployment connects at organization scope, so it cannot read or write a SHARED entity through the tenant-scoped repository — it uses the admin repository below, naming the tenants it means to act on. Per-user writes driven by the user belong on a client connected as that user.
Tenant-Isolated Repository
The standard generated Repository automatically filters all operations to the current tenant. No additional configuration is needed.
const service = new PersonRepository()
// Only returns people belonging to the current tenant
const people = await service.findAll({ page: 0, size: 10 })
All CRUD operations (save, find, update, delete, search, count) are scoped to the current tenant transparently.
Admin Repository
When you run kinotic sync on an entity with a @TenantId field, the CLI generates both a tenant-scoped repository (PersonRepository) and an admin repository (PersonAdminRepository) that can access data across tenants. The admin repository is useful for administrative dashboards, reporting, and cross-tenant operations.
const adminService = new PersonAdminRepository()
Access All Tenants
const allPeople = await adminService.findAll(['*'], { page: 0, size: 10 })
Access a Specific Tenant
const tenantPeople = await adminService.findAll(['tenant-123'], { page: 0, size: 10 })
Access Multiple Tenants
const multiPeople = await adminService.findAll(['tenant-123', 'tenant-456'], { page: 0, size: 10 })
Tenant Selection
Admin repository methods accept a tenant selection array as the first argument:
| Selection | Behavior |
|---|---|
['*'] | Access data from all tenants |
['tenant-123'] | Access data from a single tenant |
['tenant-123', 'tenant-456'] | Access data from multiple tenants |
Security Considerations
Admin repositories should be restricted to users with appropriate administrative permissions. The standard tenant-isolated repository is the default and should be used for regular application logic.