Workspaces Management (Org Admin)
Settings → Workspaces (/app/settings/workspaces). Lets an org owner/admin manage
Settings → Workspaces (/app/settings/workspaces). Lets an org owner/admin manage
every workspace they belong to and see who manages each one.
Why this exists
A B2B account can own multiple workspaces (organizations). Sub-workspaces link to a
parent via organization.parentOrgId and inherit the parent's plan. Members need a
single place to create, rename, switch between, leave, or delete workspaces, and to see
each workspace's owners/admins at a glance.
Granting/revoking a member's admin access to a workspace is done on the Team page (
/app/settings/team), not here. This page is for managing the workspaces themselves plus a read-only view of each workspace's admins.
Where it lives in the UI
- Registered in
ADTOSIGN_SETTINGS_NAV(src/config/adtosign-navigation.ts) as Workspaces → it appears in the settings sidebar, command palette, and mobile nav. - Also reachable from the org switcher's "+ Create workspace" link
(
…/workspaces?action=create, which auto-opens the create dialog).
Capabilities (CRUD)
| Action | UI | API |
|---|---|---|
| List my workspaces | SWR on load | GET /api/app/organizations |
| Create | "New Workspace" dialog (gated on plan.quotas.maxOrganizations > 1 and owner/admin role) | POST /api/app/organizations |
| Rename | row menu → Rename (owner/admin) | PATCH /api/app/organizations/[id] |
| Delete | row menu → Delete (owner only; not the active workspace; type-slug confirm) | DELETE /api/app/organizations/[id] |
| Leave | row menu → Leave (not if sole owner; not the active workspace) | DELETE /api/app/organizations/[id]/leave |
| Switch | row click / Switch button | useOrganization().switchOrganization |
| View admins | "Admins:" chips per row | GET /api/app/organizations/admins |
API surface
GET /api/app/organizations/admins
Returns the owner/admin members for every workspace the current user belongs to.
Auth: withAuthRequired. Demo users are routed to the demo DB via
getAuthDb(session.userEmail). Reads only the global DB (memberships, organizations,
users).
{
"byOrg": {
"<orgId>": [
{ "userId": "u_1", "name": "Ada Lovelace", "email": "ada@acme.com", "role": "owner" },
{ "userId": "u_2", "name": null, "email": "rob@acme.com", "role": "admin" }
]
}
}
- Only
owner/adminroles are returned; owners are sorted first. - The response only contains workspaces the caller is already a member of — no cross-tenant exposure.
Data model
No schema change. Uses existing global tables:
organization_membership(orgId,userId,role) — drives membership + role.organization— workspace identity (name,slug,parentOrgId,dataRegion).app_user— adminname/emailfor display.
Notes / gotchas
- The settings nav entry is unconditional. On single-org plans the page still renders
(one workspace + its admins); the create action shows an upgrade state when
maxOrganizations === 1. styling for/app/settings/workspaces— keep that key in sync if the route changes.
Changelog
| Date | Change | Author |
|---|---|---|
| 2026-06-04 | Surfaced Workspaces in settings nav; added GET /api/app/organizations/admins + per-workspace admin chips | Cascade |