---
title: "Org & Team Kit"
description: "Organizations, team settings, invites, allowed domains, roles, and app switching."
---

# Org & Team Kit

The Org & Team Kit standardizes how apps expose team membership and
organization context. It covers the team page, organization switcher, invites,
roles, allowed email domains, and the active organization that scopes sharing,
connections, service tokens, and app switching.

<WireframeBlock id="doc-block-toolkit-org-team">
  <Screen
    surface="desktop"
    html={
      "<div style='min-height:540px;box-sizing:border-box;padding:26px;display:grid;grid-template-columns:210px 1fr;gap:18px'><aside class='wf-card' style='display:flex;flex-direction:column;gap:10px'><small class='wf-muted'>Organization</small><div class='wf-box' style='display:flex;align-items:center;gap:8px;padding:8px 10px'><div style='width:24px;height:24px;border-radius:7px;background:var(--wf-accent-soft);display:flex;align-items:center;justify-content:center;font-size:11px;flex:none'>A</div><strong style='flex:1'>Acme</strong><span data-icon='chevronDown'></span></div><div style='display:flex;flex-direction:column;gap:4px;margin-top:4px'><div style='padding:6px 9px;border-radius:8px;background:var(--wf-accent-soft)'>Analytics</div><div style='padding:6px 9px;border-radius:8px'>Content</div><div style='padding:6px 9px;border-radius:8px'>Slides</div></div></aside><main class='wf-card' style='display:flex;flex-direction:column;gap:12px'><div style='display:flex;align-items:center;gap:10px'><h2 style='margin:0'>Team</h2><span class='wf-pill'>8 members</span><div style='flex:1'></div><button class='primary'><span data-icon='plus'></span> Invite</button></div><div style='display:flex;align-items:center;gap:10px'><div style='width:28px;height:28px;border-radius:999px;background:var(--wf-accent-soft);flex:none'></div><div style='flex:1;min-width:0'><strong>Steve Ray</strong><br/><small class='wf-muted'>steve@acme.com</small></div><span class='wf-pill'>Owner</span></div><div style='display:flex;align-items:center;gap:10px'><div style='width:28px;height:28px;border-radius:999px;background:var(--wf-accent-soft);flex:none'></div><div style='flex:1;min-width:0'><strong>Maya Chen</strong><br/><small class='wf-muted'>maya@acme.com</small></div><span class='wf-pill'>Admin <span data-icon='chevronDown'></span></span></div><div style='display:flex;align-items:center;gap:10px'><div style='width:28px;height:28px;border-radius:999px;background:var(--wf-accent-soft);flex:none'></div><div style='flex:1;min-width:0'><strong>Kai Wong</strong><br/><small class='wf-muted'>kai@acme.com</small></div><span class='wf-pill'>Member <span data-icon='chevronDown'></span></span></div><hr/><div style='display:flex;align-items:center;gap:8px'><span data-icon='mail'></span><div style='flex:1'>Anyone with an acme.com email can join</div></div><div style='display:flex;align-items:center;gap:8px'><span data-icon='user'></span><div style='flex:1'>Pending invites</div><span class='wf-pill'>3</span></div></main></div>"
    }
  />
</WireframeBlock>

## Pieces {#pieces}

| Piece                    | Purpose                                                                                          |
| ------------------------ | ------------------------------------------------------------------------------------------------ |
| `<TeamPage>`             | Standard team management page for members, invites, roles, and organization preferences.         |
| `<OrgSwitcher>`          | Account menu for the sidebar footer: organizations, Settings, Usage, app downloads, and Log out. |
| `<InvitationBanner>`     | One-click invite and domain-match join prompts.                                                  |
| `<RequireActiveOrg>`     | Guard for pages that need an active organization before rendering app data.                      |
| `useOrgRole()`           | Shared role booleans for member, invite, settings, and domain controls.                          |
| `appRoles` prop          | Adds a per-app role column and picker to `<TeamPage>` from a `defineAppRoles` descriptor.        |
| `useAppRoles()`          | An app's role vocabulary, its assignments in the active org, and whether the viewer may edit.    |
| `useAppRole()`           | The current user's assigned roles in one app, or an empty array when unassigned.                 |
| `useSetAppMemberRoles()` | Replace one member's app-role set. Org owner/admin only, enforced server-side.                   |
| Organization handlers    | Create, rename, switch, invite, accept, remove, and update roles/domains.                        |
| Roles                    | Owner, admin, and member gates for sharing, settings, service tokens, and provider connections.  |
| Allowed domains          | Automatic join and domain-scoped access policy.                                                  |
| App links                | Workspace app-link parsing and visible app-switcher helpers.                                     |

## API {#api}

Render organization context wherever data scope changes. Put `OrgSwitcher` in
the persistent shell. Organization management lives in Settings: the
Organization group's General, Members, Usage, Authentication, Apps,
Infrastructure, and Audit log pages (the last four for owners and admins).
`/team` and `/settings/organization` redirect there.

`OrgSwitcher` is the account menu (also exported as `AccountMenu`). Its button
shows the signed-in person's photo, name, and current organization, and
`compact` renders the photo alone for a collapsed rail. The menu lists the
person's email, their organizations, pending invitations, email-domain matches,
and Create organization, then Settings, Usage, and Log out. Member invitations
and organization settings live in Settings. Pass
`utilityLinks` to add a "Get apps and extensions" list for the app's downloads;
links with `external: true` open in a new tab.

```tsx
<OrgSwitcher
  compact={collapsed}
  utilityLinks={[
    {
      id: "chrome-extension",
      label: "Chrome extension",
      href: "https://example.com/extension",
      external: true,
    },
    { id: "desktop-app", label: "Get desktop app", href: "/download" },
  ]}
/>
```

```tsx
import {
  OrgSwitcher,
  RequireActiveOrg,
  TeamPage,
} from "@agent-native/toolkit/app/org";
import { useOrgRole } from "@agent-native/core/client/org-team";

export function AppShell() {
  return <OrgSwitcher />;
}

export function SettingsTeamTab() {
  return (
    <RequireActiveOrg>
      <TeamPage createOrgDescription="Create a team to share dashboards and provider connections." />
    </RequireActiveOrg>
  );
}

export function AdminOnlyControl() {
  const { canManageOrg } = useOrgRole();
  return canManageOrg ? <button type="button">Invite members</button> : null;
}
```

The Settings shell uses the Toolkit organization UI from
`@agent-native/toolkit/app/org`: `OrgGeneralPage`,
`OrgMembersPage({ appRoles? })`, `OrgAuthenticationPage`, and `OrgAppsPage`.

Server code imports the same role helpers from `@agent-native/core/org-team`.
The route plugin mounts `/_agent-native/org/*` for org creation, switching,
invites, domains, member roles, app roles, and A2A secret sync.

### Per-app roles {#app-roles}

When an app needs its own permission vocabulary on top of team membership,
declare it with `defineAppRoles` and pass the descriptor to `TeamPage`, and to
`OrgMembersPage` by replacing the Members page (see
[Settings Kit → Registering a page](/docs/toolkit-settings#registering-a-page)). The
descriptor is the serializable half of the declaration — no database imports —
so the same object can be handed to `defineAppRoles` on the server and to the
component in the browser bundle:

```tsx
import {
  RequireActiveOrg,
  TeamPage,
  useAppRole,
  useAppRoles,
  useSetAppMemberRoles,
} from "@agent-native/core/client/org-team";
import { coachRoles } from "../shared/coach-roles";

export function SettingsTeamTab() {
  return (
    <RequireActiveOrg>
      <TeamPage appRoles={coachRoles} />
    </RequireActiveOrg>
  );
}

export function CoachOnlyControl() {
  const { roles } = useAppRole("coach");
  return roles.includes("coach-admin") ? (
    <button type="button">Archive</button>
  ) : null;
}

export function RoleAdmin() {
  const { data } = useAppRoles("coach");
  const setRoles = useSetAppMemberRoles();
  if (!data?.canManage) return null;
  return (
    <button
      type="button"
      onClick={() =>
        setRoles.mutate({ appId: "coach", email: "dana@acme.com", roles: [] })
      }
    >
      Clear role
    </button>
  );
}
```

`roles`, `myRoles`, and `canManage` are progressive disclosure only. The server
re-resolves the caller's app role on every guarded operation, and assignment
edits require the org `owner`/`admin` role regardless of what the client
believes. See
[Organizations, Teams & Permissions → App roles](/docs/organizations-teams-permissions#app-roles-per-app-rbac)
for the role model and [Actions → Authorization](/docs/actions-access-control#authorize) for
the server-side `authorize` guard.

## UX Standard {#ux-standard}

- Users can see which organization they are in before creating or sharing data.
- Invites and roles live on Settings › Organization › Members, not in
  per-resource dialogs.
- App-specific roles appear as another column on that same team surface, never
  as a separate permissions page or a second organization.
- Owners and admins change the org domain, and the control explains who
  can auto-join.
- App switching lists workspace/default app links. Active-org continuity comes
  from the target app sharing the same auth and organization state.

## What's next

- [**Multi-Tenancy**](/docs/multi-tenancy) — how org scoping isolates data
  across tenants.
- [**Workspace Management**](/docs/workspace-management) — governance for
  multi-app workspaces that share an organization.
- [**Sharing Kit**](/docs/toolkit-sharing) — the access model that roles and
  org scope feed into.
