stack/docs-mintlify/guides/apps/teams/overview.mdx
BilalG1 c14a9dd3d0
feat(hexclave): PR 5 — internal symbol/path/package renames + brand strings (#1547)
## Stack Auth → Hexclave rename — PR 5 (internal symbols, paths,
packages, brand strings)

PR 5 finishes the **internal / non-wire** half of the Stack→Hexclave
rename. It only touches things where nothing outside the repo depends on
the exact name: internal symbols, file/dir names, the
`@stackframe/template` package, and residual brand strings. Plan +
progress are in `HEXCLAVE-RENAME-PR5-PLAN.md`.

Every step was verified green (`pnpm typecheck` + `pnpm lint`, 28/28)
and committed as its own checkpoint, then a fan-out of review agents
audited all commits and the findings were fixed.

### What changed
- **Internal symbols** (`@hexclave/shared`, `packages/template`, apps):
`stack*`/`Stack*` → `hexclave*`/`Hexclave*` — incl.
`stackGlobalsSymbol`, the `_Stack*AppImpl` classes,
`stackAppInternalsSymbol`, `StackContext`, `getStackStripe`, etc. The
`stack*App` local-variable convention
(`stackServerApp`/`stackClientApp`/…) was renamed across 175
source/example/doc files.
- **File renames**: `hexclave-handler/provider/context.tsx`,
`backend/hexclave.tsx`, `internal-tool/hexclave.ts`,
`hexclave-app-internals.ts`.
- **Directory renames**: `lib/hexclave-app`, `hexclave-companion`,
`[...hexclave]` route segment, `skills/hexclave`,
`dashboard/src/hexclave`, and the package dirs
**`packages/{next,shared,ui,sc,cli}`** (dropping the `stack-` prefix to
match the `@hexclave/*` npm names).
- **Packages**: `@stackframe/template` → `@hexclave/template`; **deleted
`packages/init-stack`** (onboarding lives in `@hexclave/cli init`; the
published npm package is untouched).
- **Brand strings**: reworded `Stack Auth`/`Stack dashboard` prose in
code + docs-mintlify, renamed `hexclave-app.mdx`/`use-hexclave-app.mdx`
with redirects, regenerated OpenAPI, updated coupled e2e assertions;
`doctor`/`init` now prefer `hexclave.config.ts`.

### Intentionally kept (verified, not oversights)
Wire/compat identifiers (`x-stack-*` headers, `stack-*` cookies,
`STACK_*` env names, `*.stack-auth.com`, `stackauth_`, `ask_stack_auth`,
query params), public `Stack*` SDK aliases, crypto/JWT/vault
domain-separation tags, `*-brand-sentinel`s, the
`Symbol.for("StackAuth--…")` string, `_stack_sync_metadata`, Postgres
`stackframe` / docker image names, the `stack-auth-logo*.svg` (used by
the rebrand modal), and `migration.mdx` / "formerly known as Stack Auth"
notes. False positives (Phosphor `StackIcon`/`StackSimple`, `TanStack`,
`OrbStack`, `stackable`/`Stacked` charts) left alone.

### Review pass
Six review agents audited all commits. Found + fixed one real bug — a
build script (`bundle-type-definitions.ts`) hardcoded the old
`lib/stack-app` glob path (not an import, so typecheck/lint were blind),
silently emptying the dashboard AI type bundle — plus stale comments, a
dead CI env var, and stale `.gitignore`/`.dockerignore` entries.
Cross-cutting audit confirmed **zero wire-compat identifiers were
accidentally renamed**.

### ⚠️ Verification note
`typecheck` + `lint` are fully green locally. The **e2e suite was not
run** (needs a live backend+DB), so the brand-string assertion +
OpenAPI-regen changes are verified by grep/codegen only — please let CI
exercise e2e to confirm.

### Base-branch note
This branch was forked from the local-only `cl/friendly-lewin-72293f`
(not on origin, no separate PR), so this PR against `dev` also carries
that branch's ~11 preceding Hexclave-rename commits (config-file rename,
env-var dual-read, AI setup-prompt rebrand). If those should land
separately, re-parent before merge.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Finishes the internal Stack Auth → Hexclave rename and cleans up
remaining stragglers, including dev-tool and prompt copy. All changes
are internal-only; public/wire APIs remain unchanged. Re-merged `dev`
and resolved the payments create-purchase-url conflict.

- **Refactors**
- Internal symbols: stack*/Stack* → hexclave*/Hexclave* (e.g.,
`getHexclaveServerApp` via `@/hexclave`, `getHexclaveStripe`,
`hexclaveAppInternalsSymbol`, `hexclaveSchemaInfo`, Prisma
`__hexclave_*`, `data-hexclave-handler-page`, Stripe mock
`hexclavePortPrefix`).
- Files/dirs: moved to `lib/hexclave-app`; handler route
`[...hexclave]`; backend entry `src/hexclave.tsx`; dashboard internals
`hexclave-app-internals`; companion `hexclave-companion`; dropped
`stack-` prefix across package dirs
(`packages/{shared,ui,sc,cli,next}`); workflows/emulator paths now
`packages/cli`; Quetzal codegen env at `packages/next/.env.local`.
- Packages/docs: `@stackframe/template` → `@hexclave/template`; removed
`packages/init-stack`; regenerated OpenAPI and updated docs
slugs/redirects for hexclave-app/use-hexclave-app.
- Brand strings/prompts: reworded remaining “Stack” dashboard strings to
Hexclave; updated dev-tool copy and prompts; `doctor/init` now prefer
`hexclave.config.ts`. Kept all wire-compat identifiers and public
aliases (`x-stack-*`, `stack-*` cookies, `STACK_*` env,
`*.stack-auth.com`, `Stack*` SDK names).
- Rebased/merged onto latest `dev`: retained `@hexclave/template`, kept
`src` in published files, refreshed setup-prompt imports and docs JSON,
adopted 1.0.5 version bumps, and re-merged `dev` again (resolved
`create-purchase-url` with `getHexclaveStripe`).

- **Bug Fixes**
- Restored dashboard AI type bundle by pointing the glob to
`packages/template/src/lib/hexclave-app`.
- Addressed rename leftovers: updated lingering `@/stack` imports and
CSS selector, fixed schema/meta and port-prefix expansions, and aligned
emulator commands to `packages/cli`.
- CI/build: removed a dead env var and stale ignore entries; fixed
Docker by renaming `STACK_SKIP_TEMPLATE_GENERATION` →
`HEXCLAVE_SKIP_TEMPLATE_GENERATION`.

<sup>Written for commit 3c1af3bff3.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1547?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-06-03 18:57:09 -07:00

236 lines
6.9 KiB
Plaintext

---
title: "Teams"
description: "Manage teams and team members"
sidebarTitle: "Overview"
---
Teams provide a structured way to group users and manage their permissions. Users can belong to multiple teams simultaneously, allowing them to represent departments, B2B customers, or projects.
The server can perform all operations on a team, but the client can only carry out some actions if the user has the necessary permissions. This applies to all actions that can be performed on a server/client-side `User` object and a `Team` object.
## Concepts
### Team permissions
If you attempt to perform an action without the necessary team permissions, the function will throw an error. Always check if the user has the required permission before performing any action. Learn more about permissions [here](../rbac/overview).
Here is an example of how to check if a user has a specific permission on the client
```tsx
const user = useUser({ or: 'redirect' });
const team = user.useTeam('some-team-id');
if (!team) {
return <div>Team not found</div>;
}
const hasPermission = user.usePermission(team, '$invite_members');
if (!hasPermission) {
return <div>No permission</div>;
}
// Perform corresponding action like inviting a user
```
### Team profile
A user can have a different profile for each team they belong to (Note this is different to the user's personal profile). This profile contains information like `displayName` and `profileImageUrl`. The team profile can be left empty and it will automatically take the user's personal profile information.
The team profile is visible to all the other users in the team that have the `$read_members` permission.
## Retrieving a user's teams
You can list all teams a user belongs to using the `listTeams` or `useTeams` functions or fetch a specific team with `getTeam` or `useTeam`. These functions work on both clients and servers.
<Tabs>
<Tab title="Client Component">
```tsx
const user = useUser({ or: 'redirect' });
const allTeams = user.useTeams();
const someTeam = user.useTeam('some-team-id'); // May be null if the user is not a member of this team
return (
<div>
{allTeams.map(team => (
<div key={team.id}>{team.displayName}</div>
))}
</div>
<div>
{someTeam ? someTeam.displayName : 'Not a member of this team'}
</div>
);
```
</Tab>
<Tab title="Server Component">
```tsx
const user = await hexclaveServerApp.getUser({ or: 'redirect' });
const allTeams = await user.listTeams();
const someTeam = await user.getTeam('some-team-id'); // May be null if the user is not a member of this team
return (
<div>
{allTeams.map(team => (
<div key={team.id}>{team.displayName}</div>
))}
</div>
<div>
{someTeam ? someTeam.displayName : 'Not a member of this team'}
</div>
```
</Tab>
</Tabs>
## Creating a team
To create a team, use the `createTeam` function on the `User` object. The user will be added to the team with the default team creator permissions (You can change this on the permissions tab in the Stack dashboard).
On the client side, this requires enabling the "client side team creation" on the team settings tab in the Stack dashboard.
```jsx
const team = await user.createTeam({
displayName: 'New Team',
});
```
To create a team on the server without adding a specific user, use the `createTeam` function on the `ServerApp` object:
```jsx
const team = await hexclaveServerApp.createTeam({
displayName: 'New Team',
});
```
## Updating a team
You can update a team with the `update` function on the `Team` object.
On the client, the user must have the `$update_team` permission to perform this action.
```tsx
await team.update({
displayName: 'New Name',
});
```
## Custom team metadata
You can store custom metadata on a team object, similar to the user object. The metadata can be any JSON object.
- `clientMetadata`: Can be read and updated on both the client and server sides.
- `serverMetadata`: Can only be read and updated on the server side.
- `clientReadOnlyMetadata`: Can be read on both the client and server sides, but can only be updated on the server side.
```tsx
await team.update({
clientMetadata: {
customField: 'value',
},
});
console.log(team.clientMetadata.customField); // 'value'
```
## List users in a team
You can list all users in a team with the `listUsers` function or the `useUsers` hook on the `Team` object. Note that if you want to get the team profile, you need to get it with `user.teamProfile`.
On the client, the current user must have the `$read_members` permission in the team to perform this action.
<Tabs>
<Tab title="Client Component">
```tsx
// ... retrieve the team and ensure user has the necessary permissions
const users = team.useUsers();
return (
<div>
{users.map(user => (
<div key={user.id}>{user.teamProfile.displayName}</div>
))}
</div>
);
```
</Tab>
<Tab title="Server Component">
```tsx
// ... retrieve the team
const users = await team.listUsers();
return (
<div>
{users.map(user => (
<div key={user.id}>{user.teamProfile.displayName}</div>
))}
</div>
);
```
</Tab>
</Tabs>
## Get current user's team profile
You can get the current user's team profile with the `getTeamProfile` or `useTeamProfile` function on the `User` object. This function returns the team profile for the team with the given ID.
<Tabs>
<Tab title="Client Component">
```tsx
const teamProfile = user.useTeamProfile(team);
```
</Tab>
<Tab title="Server Component">
```tsx
const teamProfile = await user.getTeamProfile(team);
```
</Tab>
</Tabs>
## Invite a user to a team
You can invite a user to a team using the `inviteUser` function on the `Team` object. The user will receive an email with a link to join the team.
On the client side, the current user must have the `$invite_members` permission to perform this action.
```tsx
await team.inviteUser(email);
```
## Adding a user to a team
If you want to add a user to a team without sending an email, use the `addUser` function on the `ServerTeam` object. This function can only be called on the server side.
```tsx
await team.addUser(user.id);
```
## Removing a user from a team
You can remove a user from a team with the `removeUser` function on the `Team` object.
On the client side, the current user must have the `$remove_members` permission to perform this action.
```tsx
await team.removeUser(user.id);
```
## Leaving a team
All users can leave a team without any permissions required.
```tsx
const team = await user.getTeam('some-team-id');
await user.leaveTeam(team);
```
## Deleting a team
You can delete a team with the `delete` function on the `Team` object.
On the client side, the current user must have the `$delete_team` permission to perform this action.
```tsx
await team.delete();
```