Multi-Tenancy
How Nebutra implements tenant isolation — data, auth, and context across every layer of the stack.
What is a tenant?
In Nebutra, a tenant is an organization — a company, team, or workspace that uses your product. Each tenant has its own:
- Members (users with roles)
- Data (isolated by tenant ID)
- Billing plan (FREE / PRO / ENTERPRISE)
- API keys and settings
- Feature flag overrides
A single user can belong to multiple tenants and switch between them without logging out.
Tenant lifecycle
1. User signs up → personal account created
2. User creates an organization → tenant provisioned
3. Tenant gets a unique org_id (e.g. org_2a8bXXXXXXXXX)
4. Tenant owner can invite members (email invitations)
5. Members receive roles: OWNER / ADMIN / MEMBER / VIEWER
6. Billing plan attached: FREE by default
7. API keys generated per tenant (not per user)The tenant context
Nebutra uses @nebutra/tenant to propagate tenant context through the entire call stack via Node.js AsyncLocalStorage. This means any code that runs in the context of a request can access the current tenant without passing it as a parameter.
import { getCurrentTenant } from "@nebutra/tenant";
// Available anywhere in the request lifecycle
const tenant = getCurrentTenant();
// → {
// tenantId: "org_2a8bXXX",
// plan: "pro",
// features: ["ai_workflows", "custom_domain"],
// rateLimits: { apiCalls: 100000, aiTokens: 5000000 }
// }Database isolation
By default, Nebutra uses shared schema with row-level security (RLS).
Every table that stores tenant-specific data has a tenant_id column. The Prisma client is automatically scoped to the current tenant using PostgreSQL RLS:
import { withRls } from "@nebutra/tenant";
import { prisma } from "@/lib/db";
// All queries on this client are automatically filtered to the current tenant
const db = withRls(prisma, tenant.tenantId);
const projects = await db.project.findMany(); // only this tenant's projectsYou cannot accidentally query another tenant's data — RLS enforces the boundary at the database level.
Tenant resolution
The API gateway resolves the current tenant from the incoming request using this priority order:
| Priority | Source | Header / Cookie |
|---|---|---|
| 1 | Explicit header | x-tenant-id |
| 2 | JWT claim | org_id in the Clerk/Better Auth token |
| 3 | Subdomain | acme.app.nebutra.com → tenant acme |
| 4 | API key prefix | nbk_live_acme_xxx → tenant acme |
Tenant switching
Users with access to multiple organizations can switch tenants from the dashboard. The active tenant is stored in a secure, HttpOnly session cookie. No re-authentication required.
Member roles
Each tenant member has exactly one role:
| Role | Description | Inherits |
|---|---|---|
OWNER | Full control, billing, can delete org | All scopes |
ADMIN | Manage members, settings, API keys | Member + Viewer scopes |
MEMBER | Core product access | Viewer scopes |
VIEWER | Read-only access | — |
See Permissions for the full scope matrix.
Org-level settings
Each tenant has an organization settings page with:
- General — Name, slug, logo
- Team — Invite, remove, change roles
- API Keys — Create, rotate, revoke API keys (SHA-256 hashed, soft-delete)
- Billing — Plan, usage, invoices
- Security — MFA requirements, session duration, IP allowlist
Related
How is this guide?
Last updated on