Persistence

Multi-Tenancy

Multi-tenant data isolation in Kinotic with automatic tenant filtering.

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 tenantPerUserEntityResult
falseMultiTenancyType.NONEOne shared dataset — every user sees every other user's rows
trueMultiTenancyType.NONEStill one shared dataset; the setting has nothing to act on
falseSHARED + @TenantIdUsers have no tenant, and writes fail with tenantId cannot be null or blank for shared multi tenancy
trueSHARED + @TenantIdEach 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:

SelectionBehavior
['*']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.

Copyright © 2026