API Reference
Complete API reference for the @authhero/multi-tenancy package.
High-Level API
initMultiTenant()
The easiest way to set up multi-tenancy. Automatically configures sync hooks, tenants API, middleware, and runtime fallback.
function initMultiTenant(config: MultiTenantConfig): MultiTenantResultParameters:
config:MultiTenantConfig- Multi-tenant configuration
Returns:
app: Hono app instance with multi-tenancy configuredcontrolPlaneTenantId: The control plane tenant ID
Example:
import { initMultiTenant } from "@authhero/multi-tenancy";
import createAdapters from "@authhero/kysely-adapter";
const { app } = initMultiTenant({
dataAdapter: createAdapters(db),
controlPlane: {
tenantId: "control_plane",
clientId: "default_client",
},
});
export default app;MultiTenantConfig
Configuration for initMultiTenant().
interface MultiTenantConfig extends Omit<AuthHeroConfig, "entityHooks" | "managementApiExtensions"> {
// Control plane configuration (optional but recommended)
controlPlane?: ControlPlaneConfig;
// Entity sync configuration (default: { resourceServers: true, roles: true })
sync?: { resourceServers?: boolean; roles?: boolean } | false;
// Default permissions for new tenant organizations
defaultPermissions?: string[];
// Whether to require organization match for tenant access
requireOrganizationMatch?: boolean;
// Custom function to get child tenant IDs
getChildTenantIds?: () => Promise<string[]>;
// Custom function to get adapters for a specific tenant
getAdapters?: (tenantId: string) => Promise<DataAdapters>;
// Additional management API extensions
managementApiExtensions?: AuthHeroConfig["managementApiExtensions"];
// Additional entity hooks
entityHooks?: AuthHeroConfig["entityHooks"];
}ControlPlaneConfig
Control plane configuration for runtime fallback and access control.
interface ControlPlaneConfig {
// The control plane tenant ID - manages all other tenants
tenantId: string;
// The control plane client ID for fallback client settings
// (web_origins, callbacks, etc. are merged with child tenant clients)
clientId: string;
}Example:
const { app } = initMultiTenant({
dataAdapter,
controlPlane: {
tenantId: "main",
clientId: "main_client",
},
});When controlPlane is configured:
- Runtime Fallback: Connection secrets, OAuth credentials, and client settings fallback to control plane without copying
- Access Control:
/api/v2/tenantsendpoint filters based on user's organization memberships - Organization Sync: Organizations are automatically created on control plane when tenants are created
Lower-Level APIs
MultiTenancyConfig
Main configuration object for lower-level multi-tenancy setup using setupMultiTenancy().
interface MultiTenancyConfig {
accessControl?: AccessControlConfig;
databaseIsolation?: DatabaseIsolationConfig;
subdomainRouting?: SubdomainRoutingConfig;
}AccessControlConfig
Configure organization-based access control.
interface AccessControlConfig {
// ID of the control plane tenant that manages other tenants
controlPlaneTenantId: string;
// Require org_id in token to match tenant being accessed (default: true)
requireOrganizationMatch?: boolean;
// Default permissions to grant to organizations
defaultPermissions?: string[];
// Default roles to assign to organizations
defaultRoles?: string[];
}Example:
{
controlPlaneTenantId: "main",
requireOrganizationMatch: true,
defaultPermissions: ["tenant:admin", "users:read", "users:write"],
defaultRoles: ["tenant-admin"],
}DatabaseIsolationConfig
Configure per-tenant database isolation.
interface DatabaseIsolationConfig {
// Get data adapters for a specific tenant
getAdapters: (tenantId: string) => Promise<DataAdapters>;
// Called when a new tenant is provisioned
onProvision?: (tenantId: string) => Promise<void>;
// Called when a tenant is deprovisioned/deleted
onDeprovision?: (tenantId: string) => Promise<void>;
}Example:
{
getAdapters: async (tenantId) => {
const db = await getTenantDatabase(tenantId);
return createAdapter(db);
},
onProvision: async (tenantId) => {
await createDatabase(tenantId);
await runMigrations(tenantId);
},
onDeprovision: async (tenantId) => {
await backupDatabase(tenantId);
await deleteDatabase(tenantId);
},
}RuntimeFallbackConfig
Configure runtime fallback for connection secrets and settings.
Use Case
Use this to share connection secrets, OAuth credentials, and SMTP settings across tenants without copying them. Sensitive data stays in the control plane.
interface RuntimeFallbackConfig {
// Control plane tenant ID for connection/setting fallbacks
controlPlaneTenantId?: string;
// Control plane client ID for client setting fallbacks
controlPlaneClientId?: string;
}Example:
import { withRuntimeFallback } from "@authhero/multi-tenancy";
const adapters = withRuntimeFallback(baseAdapters, {
controlPlaneTenantId: "control_plane",
controlPlaneClientId: "control_plane_client"
});See also: Runtime Fallback Guide
SubdomainRoutingConfig
Configure subdomain-based tenant routing.
interface SubdomainRoutingConfig {
// Base domain for tenant subdomains (e.g., "auth.example.com")
baseDomain: string;
// Use organizations to resolve subdomains (default: true)
useOrganizations?: boolean;
// Custom subdomain resolver function
resolveSubdomain?: (
subdomain: string,
context: Context,
) => Promise<string | null>;
// Subdomains reserved for system use
reservedSubdomains?: string[];
}Example:
{
baseDomain: "auth.example.com",
useOrganizations: true,
reservedSubdomains: ["www", "api", "admin"],
resolveSubdomain: async (subdomain) => {
const tenant = await db.tenants.findBySubdomain(subdomain);
return tenant?.id || null;
},
}Factory Functions
setupMultiTenancy()
Creates a complete multi-tenancy setup with hooks, middleware, and routes.
function setupMultiTenancy(config: MultiTenancyConfig): {
hooks: MultiTenancyHooks;
middleware: MiddlewareHandler;
app: Hono;
config: MultiTenancyConfig;
};Parameters:
config: Multi-tenancy configuration
Returns:
hooks: Hook functions to integrate with AuthHeromiddleware: Combined middleware for access control, subdomain routing, and database resolutionapp: Hono app with tenant management routesconfig: Resolved configuration
Example:
const multiTenancy = setupMultiTenancy({
accessControl: {
controlPlaneTenantId: "main",
},
});
app.use("*", multiTenancy.middleware);
app.route("/management", multiTenancy.app);
app.route("/", createAuthhero({ hooks: multiTenancy.hooks }));createMultiTenancyHooks()
Creates hook functions for AuthHero integration.
function createMultiTenancyHooks(config: MultiTenancyConfig): MultiTenancyHooks;Parameters:
config: Multi-tenancy configuration
Returns:
- Hook functions implementing the AuthHero hooks interface
Example:
const hooks = createMultiTenancyHooks({
accessControl: { controlPlaneTenantId: "main" },
databaseIsolation: { getAdapters: factory.getAdapters },
});
const app = createAuthhero({
hooks: {
...hooks,
// Your custom hooks
},
});createMultiTenancy()
Creates a Hono app with tenant management routes.
function createMultiTenancy(config: MultiTenancyConfig): Hono;Parameters:
config: Multi-tenancy configuration
Returns:
- Hono app with CRUD routes for tenant management
Example:
const tenantApp = createMultiTenancy({
accessControl: { controlPlaneTenantId: "main" },
});
app.route("/management", tenantApp);Middleware Functions
createMultiTenancyMiddleware()
Creates combined middleware for access control, subdomain routing, and database resolution.
function createMultiTenancyMiddleware(
config: MultiTenancyConfig,
): MiddlewareHandler;Parameters:
config: Multi-tenancy configuration
Returns:
- Hono middleware handler
Example:
const middleware = createMultiTenancyMiddleware({
accessControl: { controlPlaneTenantId: "main" },
subdomainRouting: { baseDomain: "auth.example.com" },
});
app.use("*", middleware);createAccessControlMiddleware()
Creates middleware for organization-based access control.
function createAccessControlMiddleware(
config: MultiTenancyConfig,
): MiddlewareHandler;Example:
const accessControl = createAccessControlMiddleware({
accessControl: {
controlPlaneTenantId: "main",
requireOrganizationMatch: true,
},
});
app.use("*", accessControl);createSubdomainMiddleware()
Creates middleware for subdomain-based tenant routing.
function createSubdomainMiddleware(
config: MultiTenancyConfig,
): MiddlewareHandler;Example:
const subdomainRouter = createSubdomainMiddleware({
subdomainRouting: {
baseDomain: "auth.example.com",
reservedSubdomains: ["www", "api"],
},
});
app.use("*", subdomainRouter);createDatabaseMiddleware()
Creates middleware for per-tenant database resolution.
function createDatabaseMiddleware(
config: MultiTenancyConfig,
): MiddlewareHandler;Example:
const dbMiddleware = createDatabaseMiddleware({
databaseIsolation: {
getAdapters: factory.getAdapters,
},
});
app.use("*", dbMiddleware);createProtectSyncedMiddleware()
Creates middleware to protect system resources from modification.
function createProtectSyncedMiddleware(): MiddlewareHandler;Example:
const protect = createProtectSyncedMiddleware();
app.use("/api/v2/*", protect);Adapter Functions
createRuntimeFallbackAdapter()
Creates a wrapped adapter with runtime fallback functionality from control plane.
function createRuntimeFallbackAdapter(
baseAdapters: DataAdapters,
config: RuntimeFallbackConfig,
): DataAdapters;Parameters:
baseAdapters: DataAdapters- The base data adapters to wrapconfig: RuntimeFallbackConfig- Configuration for runtime fallback
Returns:
DataAdapters- Wrapped adapters with fallback functionality
Example:
import { createRuntimeFallbackAdapter } from "@authhero/multi-tenancy";
const adapters = createRuntimeFallbackAdapter(baseAdapters, {
controlPlaneTenantId: "control_plane",
controlPlaneClientId: "control_plane_client"
});See also: Runtime Fallback Guide
withRuntimeFallback()
Convenience helper for createRuntimeFallbackAdapter.
function withRuntimeFallback(
baseAdapters: DataAdapters,
config: RuntimeFallbackConfig,
): DataAdapters;Parameters:
baseAdapters: DataAdapters- The base data adapters to wrapconfig: RuntimeFallbackConfig- Configuration for runtime fallback
Returns:
DataAdapters- Wrapped adapters with fallback functionality
Example:
import { withRuntimeFallback } from "@authhero/multi-tenancy";
const adapters = withRuntimeFallback(baseAdapters, {
controlPlaneTenantId: "control_plane",
controlPlaneClientId: "control_plane_client"
});Database Factory
DatabaseFactory Interface
Interface for implementing per-tenant database factories.
interface DatabaseFactory {
// Get data adapters for a tenant's database
getAdapters(tenantId: string): Promise<DataAdapters>;
// Provision a new database for a tenant
provision(tenantId: string): Promise<void>;
// Deprovision (delete) a tenant's database
deprovision(tenantId: string): Promise<void>;
}Example Implementation:
const factory: DatabaseFactory = {
async getAdapters(tenantId: string) {
const db = await getDatabaseConnection(tenantId);
return createAdapter(db);
},
async provision(tenantId: string) {
await createDatabase(tenantId);
await runMigrations(tenantId);
},
async deprovision(tenantId: string) {
await backupDatabase(tenantId);
await deleteDatabase(tenantId);
},
};Hooks
MultiTenancyHooks Interface
Hook functions that integrate with AuthHero's lifecycle.
interface MultiTenancyHooks {
// Called before creating a tenant
onTenantCreating?: (tenant: Tenant) => Promise<void>;
// Called after a tenant is created
onTenantCreated?: (tenant: Tenant) => Promise<void>;
// Called before updating a tenant
onTenantUpdating?: (oldTenant: Tenant, newTenant: Tenant) => Promise<void>;
// Called after a tenant is updated
onTenantUpdated?: (oldTenant: Tenant, newTenant: Tenant) => Promise<void>;
// Called before deleting a tenant
onTenantDeleting?: (tenant: Tenant) => Promise<void>;
// Called after a tenant is deleted
onTenantDeleted?: (tenant: Tenant) => Promise<void>;
}Example:
const hooks: MultiTenancyHooks = {
onTenantCreated: async (tenant) => {
console.log(`Tenant ${tenant.id} created`);
await sendWelcomeEmail(tenant);
},
onTenantDeleting: async (tenant) => {
console.log(`Tenant ${tenant.id} will be deleted`);
await backupTenantData(tenant.id);
},
};Management Routes
The tenant management app exposes the following routes:
GET /tenants
List all tenants (main tenant only).
Headers:
Authorization: Bearer <token>- Token without org_id
Query Parameters:
page?: number- Page number (default: 1)per_page?: number- Items per page (default: 50, max: 100)
Response:
{
tenants: Tenant[];
total: number;
page: number;
per_page: number;
}POST /tenants
Create a new tenant (main tenant only).
Headers:
Authorization: Bearer <token>- Token without org_idContent-Type: application/json
Body:
{
id: string;
name: string;
friendly_name?: string;
// ... other tenant fields
}Response:
{
tenant: Tenant;
}GET /tenants/:id
Get a specific tenant (main tenant only).
Headers:
Authorization: Bearer <token>- Token without org_id
Response:
{
tenant: Tenant;
}PATCH /tenants/:id
Update a tenant (main tenant only).
Headers:
Authorization: Bearer <token>- Token without org_idContent-Type: application/json
Body:
{
name?: string;
friendly_name?: string;
// ... other fields to update
}Response:
{
tenant: Tenant;
}DELETE /tenants/:id
Delete a tenant (main tenant only).
Headers:
Authorization: Bearer <token>- Token without org_id
Response:
{
success: boolean;
}Context Variables
Variables available in request context after middleware:
tenant_id
The resolved tenant ID for the current request.
app.get("/api/users", async (c) => {
const tenantId = c.get("tenant_id");
// Use tenantId...
});org_id
The organization ID from the JWT token (if present).
app.get("/api/users", async (c) => {
const orgId = c.get("org_id");
// Use orgId...
});Type Exports
Tenant
interface Tenant {
id: string;
name: string;
friendly_name?: string;
created_at: string;
updated_at: string;
// ... additional fields
}DataAdapters
interface DataAdapters {
users: UserAdapter;
applications: ApplicationAdapter;
connections: ConnectionAdapter;
// ... other adapters
}Next Steps
- Architecture - Understanding the multi-tenancy model
- Database Isolation - Per-tenant databases
- Migration Guide - Migrate from single to multi-tenant