> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vertz.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-Tenancy

> TenantProvider, useTenant() hook, and TenantSwitcher component for multi-tenant apps

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

```tsx theme={null}
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

```tsx theme={null}
import { TenantSwitcher } from '@vertz/ui-auth';

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

### 3. Or build a custom UI with the hook

```tsx theme={null}
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.

```tsx theme={null}
<TenantProvider
  listTenants={auth.listTenants}
  switchTenant={auth.switchTenant}
  onSwitchComplete={(tenantId) => {
    /* ... */
  }}
>
  <App />
</TenantProvider>
```

| Prop               | Type                                         | Description                                           |
| ------------------ | -------------------------------------------- | ----------------------------------------------------- |
| `listTenants`      | `() => Promise<Result<ListTenantsResponse>>` | SDK method to fetch tenants (generated by codegen)    |
| `switchTenant`     | `SdkMethod<{ tenantId: string }>`            | SDK method to switch tenant (generated by codegen)    |
| `onSwitchComplete` | `(tenantId: string) => void`                 | Called after a successful switch — use for navigation |
| `children`         | `unknown`                                    | App content                                           |

<Note>
  `TenantProvider` must be rendered inside `AuthProvider`. It reads from `AuthContext` internally —
  you don't need to pass auth state manually.
</Note>

### 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.

```ts theme={null}
const tenant = useTenant();
```

| Property            | Type                                                 | Description                       |
| ------------------- | ---------------------------------------------------- | --------------------------------- |
| `tenants`           | `TenantInfo[]`                                       | All tenants the user belongs to   |
| `currentTenantId`   | `string \| undefined`                                | Tenant in the current session     |
| `lastTenantId`      | `string \| undefined`                                | Last tenant the user switched to  |
| `resolvedDefaultId` | `string \| undefined`                                | Server-recommended default tenant |
| `isLoading`         | `boolean`                                            | `true` while fetching tenants     |
| `switchTenant`      | `(tenantId: string) => Promise<Result<void, Error>>` | Switch to a different tenant      |

### TenantInfo

```ts theme={null}
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`:

```tsx theme={null}
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"
/>;
```

| Prop         | Type                              | Default               | Description                                               |
| ------------ | --------------------------------- | --------------------- | --------------------------------------------------------- |
| `renderItem` | `(tenant: TenantInfo) => unknown` | Renders `tenant.name` | Custom render for each tenant item (trigger and dropdown) |
| `className`  | `string`                          | —                     | CSS class for the container                               |

### Styling

`TenantSwitcher` uses `data-part` attributes for styling:

| Part              | Element         | Description              |
| ----------------- | --------------- | ------------------------ |
| `tenant-switcher` | Container `div` | Root wrapper             |
| `trigger`         | `button`        | Shows the current tenant |
| `content`         | `div`           | Dropdown panel           |
| `item`            | `button`        | Individual tenant option |

Items have `data-selected="true"` when they match the current tenant.

```css theme={null}
[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:

```tsx theme={null}
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

```tsx theme={null}
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

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

### With custom avatars per tenant

```tsx theme={null}
<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](/guides/server/multi-tenancy) for:

* Configuring `tenant.verifyMembership`, `tenant.listTenants`, and `tenant.resolveDefault`
* How tenant-scoped JWTs work
* Automatic entity tenant scoping
* Endpoint reference

***

## Next steps

<CardGroup cols={2}>
  <Card title="Server Multi-Tenancy" icon="server" href="/guides/server/multi-tenancy">
    Configure tenant endpoints, membership verification, and auto-resolve.
  </Card>

  <Card title="Authentication" icon="lock" href="/guides/ui/auth">
    AuthProvider, useAuth(), and session management.
  </Card>

  <Card title="Access Control" icon="shield" href="/guides/ui/access-control">
    Entitlements and tenant-scoped access rules.
  </Card>
</CardGroup>
