requireK99Admin & multi-tenancy
How K99 isolates tenant data with the requireK99Admin and requireKaturaAdmin guards.
Overview
Katura is multi-tenant by design β many stores, one codebase, one database. This article explains how K99 keeps tenant data isolated and how you, as a developer or integrator, should reason about it.
The tenancy model
- Every meaningful row in the database has a
tenantIdcolumn. - Routes under
/admin/*resolve the active tenant from the session and pin it for the request. - Routes under
/k99/*are explicitly cross-tenant and require the platform-level role. - Public storefront routes resolve the tenant from the host header (
store.example.com) at the edge.
Server guards
Three named guards do almost all the heavy lifting:
| Guard | Where | Returns |
|---|---|---|
requireKaturaAdmin() | Every /admin/* route handler & server component | { user, tenant, role } or redirects to /login |
requireK99Admin() | Every /k99/* route handler | { user } or 404 (we 404, not 403, on purpose) |
resolveTenant(req) | Storefront edge middleware | The tenant matching the host, or null |
Warning
Never query the database directly without going through a tenant-aware client. The Prisma middleware will throw MissingTenantContext if you try.
Custom domains
Tenants can bring their own domain. The flow:
- Tenant adds the domain in
/admin/settings/domains. - Katura issues an SSL certificate via Vercel.
- Edge middleware learns the host β tenant mapping at deploy time + via on-demand lookup.
- Storefront routes immediately resolve to that tenant.
Data export & portability
Every tenant can export 100% of their data at any time from /admin/account/data-export. Exports include products, customers, orders, CRM history, CAD files, Yen transcripts, and Outbound campaign history β in a documented JSON+CSV bundle.
