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 projects

You 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:

PrioritySourceHeader / Cookie
1Explicit headerx-tenant-id
2JWT claimorg_id in the Clerk/Better Auth token
3Subdomainacme.app.nebutra.com → tenant acme
4API key prefixnbk_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:

RoleDescriptionInherits
OWNERFull control, billing, can delete orgAll scopes
ADMINManage members, settings, API keysMember + Viewer scopes
MEMBERCore product accessViewer scopes
VIEWERRead-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

How is this guide?

Edit on GitHub

Last updated on

On this page