---
title: Organizations
description: Teams, roles, and the session acting as one.
---

import Callout from '../../../components/Callout.astro';


An organization is a group of your users. A session can act **as** an
organization, and while it does, the tokens it holds say so.

## The active organization

```tsx
const { organizations, setActive } = useOrganizationList();
const { organization } = useOrganization();

await setActive('org_...');  // null returns to personal scope
```

Switching **rotates the session's tokens**, because the organization is a claim
inside them. An app holding the old access token would keep acting as the old
organization until it expired, which is why the rotation is not optional.

## Roles and permissions

A member holds a role, and a role grants permissions. Your backend receives
both in the token:

```json
{
  "sub": "user_...",
  "org_id": "org_...",
  "org_role": "admin",
  "org_permissions": ["org:members:read", "org:members:manage"]
}
```

Permissions are sent alongside the role rather than left for you to derive from
it. The point of a permission model is that the mapping can change without
every consumer reimplementing it.

Check them in the UI:

```tsx
<Protect permission="org:members:manage">
  <InviteForm />
</Protect>
```

And on your server, which is where it counts:

```ts
const auth = await getAuth(request, { environmentId });
if (!auth.has({ permission: 'org:members:manage' })) {
  return new Response('Forbidden', { status: 403 });
}
```

## Managing members

```ts
await zevauth.organizations.addMember({ email: 'colleague@example.com' });
await zevauth.organizations.setMemberRole({ userId, role: 'admin' });
await zevauth.organizations.removeMember({ userId });
```

Adding somebody requires them to have an account with you already.

<Callout type="info">
Authorisation for these is checked against the **live membership**, not the
token's claims. Claims are a snapshot: an admin demoted a minute ago still
carries the old permissions until their next refresh. That is fine for deciding
what to draw and not fine for removing somebody from a company.
</Callout>

Two rules apply underneath:

**Anybody may remove themselves.** An organization you cannot leave is a trap,
so leaving needs no permission.

**The last owner cannot be removed** by anyone, including themselves.
Otherwise the organization would be left with nobody able to administer it.

## In the UI

```tsx
<OrganizationSwitcher />
<OrganizationProfile />
```

`<OrganizationProfile>` decides what to show from the token's claims, so a
control can briefly be visible to somebody who was just demoted. Pressing it
produces a refusal, not a change. Hiding the button is a courtesy; refusing the
request is the security.

## Changing the organization

Renaming an organization or changing its picture needs the `org:manage`
permission — a different one from `org:members:manage`, because plenty of teams
want people who can invite colleagues without being able to rename the company
on every screen in the product.

```ts
await client.organizations.update({ name: 'Acme Corp' });
```

The picture works the same way as a user's avatar: upload, then save.

```ts
const url = await client.organizations.uploadImage({ file });
await client.organizations.update({ imageUrl: url });
```

`imageUrl: null` removes it. Omitting the field leaves it alone.

The permission is checked against the caller's **live** membership, not against
the `org_permissions` claim in their token. Claims are a snapshot from when the
token was minted, so somebody demoted a minute ago still carries the old ones
until their next refresh. That is fine for deciding what to draw and not enough
to decide what to allow.