X Enterprises
nuxt-x-tenancy

Resolution chain

How nuxt-x-tenancy resolves the active tenant — path, subdomain, and header strategies, middleware ordering, and the upstream better-auth slug-lookup caveat.

Resolution chain

The tenant.global middleware (installed by the layer, on unless xTenancy.enabled: false) resolves the active tenant according to xTenancy.strategy. It runs after auth-better's auth.global/organization.global (alphabetical order), so when xAuth.organization.enabled is on, auth-better's org routes already activate the org — the tenancy middleware short-circuits on tenant.slug === slug.

Strategies

path (default)

Reads the slug from ${pathPrefix}/:slug — default /org/:slug, matching auth-better's org routes — and activates it via resolveBySlug(). Unresolvable slugs redirect to xTenancy.fallbackRoute when set.

export default defineAppConfig({
  xTenancy: {
    strategy: 'path',
    pathPrefix: '/org',
    fallbackRoute: '/org', // redirect target for unknown slugs
  },
})

subdomain

Maps the request host against xTenancy.rootDomain: the left-most host label is the organization slug (acme.app.example.comacme), activated via the standard set-active endpoint. Requires wildcard DNS / vhost for *.{rootDomain} routing to the Nuxt app.

export default defineAppConfig({
  xTenancy: {
    strategy: 'subdomain',
    rootDomain: 'app.example.com', // required for this strategy
  },
})

No routing at all — the middleware no-ops. The tenant follows the session's active organization, and only the X-Tenant-ID request header carries it to APIs. Use this for API-only tenant scoping or when tenancy is orthogonal to the URL space.

Slug resolution caveat (upstream better-auth bug)

resolveBySlug() resolves slugs against the organization/list response, not get-full-organization?organizationSlug=. Verified against better-auth 1.6.5: the client's organization.getFullOrganization({ organizationId, organizationSlug }) silently drops those GET params, so slug lookup via that endpoint always returns the active org. The list is the reliable client-side slug → id source.

This upstream bug also affects auth-better's own organization.global slug sync and direct SSR hits of /org/:slug (the better-auth atoms resolve client-side only, so a direct server-rendered /org/:slug bounces to /org). Navigate org routes client-side until the upstream fix lands.


AI Context

guide: resolution-chain
package: "@xenterprises/nuxt-x-tenancy"
use-when: >
  Choosing or debugging the tenant resolution strategy. path (default,
  /org/:slug), subdomain (host label vs rootDomain), header (no routing,
  X-Tenant-ID only). Middleware runs after auth-better's globals.
  resolveBySlug uses organization/list because better-auth's
  getFullOrganization drops slug GET params (≤1.6.5).
Copyright © 2026