Multitenant

Getting started

Install, config, validation, and wiring adapters.

For scope and mental model, read Why Multitenant? first.

The CLI is published on npm as @multitenant/cli (binary name multitenant). Use npx @multitenant/cli … so npx resolves the published package; there is no multitenant package at the root of the registry.

1. Add config

Option A — scaffold

npx @multitenant/cli init
# optional: --framework next-app | next-pages | express
npx @multitenant/cli check

Option B — minimal hand-written file

Create tenants.config.json at the project root:

{
  "version": 1,
  "defaultEnvironment": "local",
  "markets": {
    "us": {
      "currency": "USD",
      "locale": "en-US",
      "timezone": "America/New_York"
    }
  },
  "tenants": {
    "us-main": {
      "market": "us",
      "domains": {
        "local": { "us.localhost": "us-main" },
        "production": { "us.example.com": "us-main" }
      }
    }
  }
}

2. Validate

npx @multitenant/cli check

On failure you get the same messages as validateTenantsConfig in code (InvalidTenantsConfigError). See Errors.

3. Wire your stack

StackPackageWhat you use
Next.js App Router@multitenant/next-appMiddleware + getTenantFromHeaders / requireTenant
Next.js Pages@multitenant/next-pageswithTenantGSSP, withTenantApi
React@multitenant/reactTenantProvider, useTenant, useMarket, …
Express@multitenant/expressmultitenantExpress()req.tenant
Nest@multitenant/nestMultitenantModuleForRoot, @Tenant()

Meta-install line (re-exports several packages): @multitenant/next.

4. Local dev with real hostnames

Start your app (e.g. port 3000), then in another terminal:

npx @multitenant/cli dev --target http://localhost:3000 --port 3100

Open http://us.localhost:3100 (or whatever host keys you have under domains.local for the active --envnot bare http://localhost:3100). The proxy resolves the tenant from the Host header; localhost alone does not map to a tenant, so the proxy returns a small “No tenant for host” page. The proxy injects x-tenant-key, x-market-key, etc. on the forwarded request.

Same app on port 3000: if npm run dev is already listening (e.g. http://localhost:3000), you can open http://us.localhost:3000 without the proxy: the browser still sends Host: us.localhost, so middleware can resolve the tenant. Use the proxy when you want one entry URL on 3100 plus injected headers / a single “front door” while the app stays on 3000.

Optional: npx @multitenant/cli dev --run-dev to let the CLI spawn npm run dev for you. Full options: CLI: dev, check, print.

Async or remote config (bootstrap only)

createTenantRegistry is synchronous — pass an already-loaded TenantsConfig. Load config once at bootstrap (disk, HTTP, CMS map → plain object), validateTenantsConfig, then keep one registry instance per worker.

Load from disk (Node)

import { createTenantRegistry } from '@multitenant/core';
import { loadTenantsConfig } from '@multitenant/config';

const config = await loadTenantsConfig(); // or loadTenantsConfig({ cwd: '/app' })
export const registry = createTenantRegistry(config);

Validate after fetch / CMS mapping

import { createTenantRegistry } from '@multitenant/core';
import { validateTenantsConfig } from '@multitenant/config';

async function bootstrapRegistryFromUrl() {
  const res = await fetch(process.env.TENANTS_CONFIG_URL!, { headers: { Accept: 'application/json' } });
  if (!res.ok) throw new Error(`config fetch ${res.status}`);
  const raw: unknown = await res.json();
  const config = validateTenantsConfig(raw);
  return createTenantRegistry(config);
}

Refresh / hot reload of config in a long-lived process is your concern (replace the registry handle, or restart).

In Next.js, avoid top-level await in modules Edge may load: bootstrap in instrumentation.ts (Node) or a lazy server-only module — see Next.js — App Router.

After bootstrap, pass registry into createTenantMiddleware, multitenantExpress, withTenantGSSP, etc. Client bundles should receive ResolvedTenant (or derived props), not the full TenantsConfig, unless you intentionally expose topology.

On this page