Skip to main content
TenantProvider gives your app tenant switching: list tenants, switch between them, auto-resolve the default, and track loading state. Wrap your app (inside AuthProvider), use useTenant() to read state, and drop in TenantSwitcher for a ready-made UI — or build your own.

Quick start

1. Wrap your app

TenantProvider must be inside AuthProvider. Pass the SDK methods generated by codegen:
import { AuthProvider, TenantProvider } from 'vertz/ui/auth';
import { auth } from './.vertz/generated/auth';

function App() {
  return (
    <AuthProvider>
      <TenantProvider
        listTenants={auth.listTenants}
        switchTenant={auth.switchTenant}
        onSwitchComplete={(tenantId) => {
          // Navigate, refetch data, etc.
          navigate({ to: '/dashboard' });
        }}
      >
        <AppContent />
      </TenantProvider>
    </AuthProvider>
  );
}

2. Drop in the switcher

import { TenantSwitcher } from 'vertz/ui-auth';

function Sidebar() {
  return (
    <nav>
      <TenantSwitcher />
    </nav>
  );
}

3. Or build a custom UI with the hook

import { useTenant } from 'vertz/ui/auth';

function CustomTenantPicker() {
  const { tenants, currentTenantId, switchTenant, isLoading } = useTenant();

  if (isLoading) return <div>Loading tenants...</div>;

  return (
    <div>
      {tenants.map((tenant) => (
        <button
          key={tenant.id}
          onClick={() => switchTenant(tenant.id)}
          data-active={tenant.id === currentTenantId ? 'true' : undefined}
        >
          {tenant.name}
        </button>
      ))}
    </div>
  );
}

TenantProvider

Fetches the tenant list on mount and provides tenant state to all children via context.
<TenantProvider
  listTenants={auth.listTenants}
  switchTenant={auth.switchTenant}
  onSwitchComplete={(tenantId) => {
    /* ... */
  }}
>
  <App />
</TenantProvider>
PropTypeDescription
listTenants() => Promise<Result<ListTenantsResponse>>SDK method to fetch tenants (generated by codegen)
switchTenantSdkMethod<{ tenantId: string }>SDK method to switch tenant (generated by codegen)
onSwitchComplete(tenantId: string) => voidCalled after a successful switch — use for navigation
childrenunknownApp content
TenantProvider must be rendered inside AuthProvider. It reads from AuthContext internally — you don’t need to pass auth state manually.

Automatic query invalidation on tenant switch

When switchTenant() succeeds, all active tenant-scoped queries are automatically invalidated. Cached data from the previous tenant is cleared immediately (no stale cross-tenant data visible), then each query refetches with the new tenant context. Non-tenant-scoped queries (e.g., global settings) are left untouched. This happens automatically — no manual invalidation or onSwitchComplete callback needed for data freshness.

useTenant()

Returns reactive tenant state. All signal properties are auto-unwrapped by the compiler.
const tenant = useTenant();
PropertyTypeDescription
tenantsTenantInfo[]All tenants the user belongs to
currentTenantIdstring | undefinedTenant in the current session
lastTenantIdstring | undefinedLast tenant the user switched to
resolvedDefaultIdstring | undefinedServer-recommended default tenant
isLoadingbooleantrue while fetching tenants
switchTenant(tenantId: string) => Promise<Result<void, Error>>Switch to a different tenant

TenantInfo

interface TenantInfo {
  id: string;
  name: string;
  [key: string]: unknown; // logo, plan, role — whatever you return from listTenants
}

TenantSwitcher

A ready-made dropdown component that renders the current tenant with a menu to switch. Import from @vertz/ui-auth:
import { TenantSwitcher } from 'vertz/ui-auth';

<TenantSwitcher
  renderItem={(tenant) => (
    <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
      <img src={tenant.logo} width={20} height={20} />
      <span>{tenant.name}</span>
    </div>
  )}
  className="my-tenant-picker"
/>;
PropTypeDefaultDescription
renderItem(tenant: TenantInfo) => unknownRenders tenant.nameCustom render for each tenant item (trigger and dropdown)
classNamestringCSS class for the container

Styling

TenantSwitcher uses data-part attributes for styling:
PartElementDescription
tenant-switcherContainer divRoot wrapper
triggerbuttonShows the current tenant
contentdivDropdown panel
itembuttonIndividual tenant option
Items have data-selected="true" when they match the current tenant.
[data-part='tenant-switcher'] {
  position: relative;
}
[data-part='trigger'] {
  /* trigger button styles */
}
[data-part='content'] {
  /* dropdown styles */
}
[data-part='item'] {
  /* item styles */
}
[data-part='item'][data-selected] {
  /* selected item styles */
}

Auto-resolve on login

When a user logs in, their session has no tenantId. The resolvedDefaultId from useTenant() tells you which tenant the server recommends — typically the last-accessed tenant. Use this to auto-switch on app mount:
import { useTenant } from 'vertz/ui/auth';
import { watch } from 'vertz/ui';

function TenantAutoResolver() {
  const { resolvedDefaultId, currentTenantId, switchTenant, isLoading } = useTenant();

  watch(
    () => isLoading,
    (loading) => {
      if (!loading && resolvedDefaultId && !currentTenantId) {
        switchTenant(resolvedDefaultId);
      }
    },
  );

  return null;
}
Place this inside TenantProvider. After the tenant list loads, it auto-switches to the resolved default if no tenant is currently selected.

Common patterns

Tenant name in header

function Header() {
  const { tenants, currentTenantId } = useTenant();
  const current = tenants.find((t) => t.id === currentTenantId);

  return (
    <header>
      <span>{current?.name ?? 'No tenant selected'}</span>
    </header>
  );
}

Redirect after switch

<TenantProvider
  listTenants={auth.listTenants}
  switchTenant={auth.switchTenant}
  onSwitchComplete={() => {
    // Redirect to dashboard after switching
    navigate({ to: '/dashboard' });
  }}
>

With custom avatars per tenant

<TenantSwitcher
  renderItem={(tenant) => (
    <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
      <Avatar src={tenant.logo} fallback={tenant.name[0]} size="sm" />
      <span>{tenant.name}</span>
    </div>
  )}
/>

Server setup

The client-side tenant features require server-side configuration. See the server multi-tenancy guide for:
  • Configuring tenant.verifyMembership, tenant.listTenants, and tenant.resolveDefault
  • How tenant-scoped JWTs work
  • Automatic entity tenant scoping
  • Endpoint reference

Next steps

Server Multi-Tenancy

Configure tenant endpoints, membership verification, and auto-resolve.

Authentication

AuthProvider, useAuth(), and session management.

Access Control

Entitlements and tenant-scoped access rules.