Ga naar hoofdinhoud

useTenantContext

Multi-tenancy composable for Conduction apps consuming the OpenRegister MultiTenancyTrait. One shared activeOrganisationUuid flows top-to-bottom via Vue provide/inject; every outbound request, every <CnFormDialog> instance, and every store action picks up the active tenant automatically.

Spec: openspec/changes/multi-tenancy-context.

When to use

  • An app where users belong to multiple organisations and the active organisation must scope CRUD reads/writes server-side via the X-OpenRegister-Organisation header.
  • An app that already declares an organisation field on its schemas and wants the form dialogs to auto-stamp the active tenant on create.

Single-tenant deployments can leave the code paths in place — without a setActiveTenant() call the composable silently no-ops.

Signature

import { useTenantContext } from '@conduction/nextcloud-vue'

const {
activeOrganisationUuid, // Ref<string|null>
activeOrganisation, // Ref<object|null>
setActiveTenant, // (uuid: string | { uuid, organisation? }, organisation?) => void
onTenantSwitch, // (cb) => off()
} = useTenantContext()

Return value

KeyTypeDescription
activeOrganisationUuidRef<string | null>Current tenant UUID; null outside a provider or before the first setActiveTenant() call.
activeOrganisationRef<object | null>Resolved organisation entity. Lags activeOrganisationUuid when the consumer only knows the UUID.
setActiveTenantFunctionUpdate the tenant. Accepts (uuid), (uuid, organisation), or ({ uuid, organisation? }).
onTenantSwitchFunctionSubscribe to tenantSwitch events. Returns an off() deregister handle.

Provider

Mount the provider at the top of the component tree. CnAppRoot already does this for you — pass :initial-organisation-uuid and :initial-organisation to seed from server-rendered state:

<CnAppRoot
:manifest="manifest"
:initial-organisation-uuid="bootstrap.activeOrgUuid"
:initial-organisation="bootstrap.activeOrg" />

Without CnAppRoot, call provideTenantContext yourself in your shell's setup().

Consuming the context

Composition API

import { useTenantContext } from '@conduction/nextcloud-vue'

export default {
setup() {
const { activeOrganisationUuid, setActiveTenant, onTenantSwitch } = useTenantContext()

const off = onTenantSwitch(({ uuid }) => {
// React to switches across the whole app
console.log('tenant switched to', uuid)
})

return { activeOrganisationUuid, setActiveTenant, off }
},
}

Options API

import { tenantContextMixin } from '@conduction/nextcloud-vue'

export default {
mixins: [tenantContextMixin],

created() {
this.onTenantSwitch(({ uuid }) => this.refreshList(uuid))
},

methods: {
switch(uuid) {
this.setActiveTenant(uuid)
},
},
}

What the provider stamps

When a tenant is active:

  • Every useObjectStore() / createObjectStore() fetch stamps X-OpenRegister-Organisation: <uuid> (buildHeaders({ organisationUuid })).
  • Every createCrudStore() action does the same.
  • CnIndexPage watches :active-organisation and forwards changes via setActiveTenantOrganisation(uuid) so the next fetchCollection() hits the correct tenant.
  • CnFormDialog and CnAdvancedFormDialog auto-fill the organisation field on create when the schema declares one and the form data is empty.
  • CnTenantBadge renders the active organisation in the top-bar slot of CnAppRoot.

Single-tenant fallback

useTenantContext() outside a provider returns a noop fallback: activeOrganisationUuid.value is null, setActiveTenant() logs a console.warn and writes to a stand-alone context (so consumer code doesn't crash). Header stamping silently no-ops; the schema auto-fill silently no-ops.