Members & Roles
The Team Members page (/settings/members) manages who has access to the current workspace
and what permissions they have.
Role Hierarchy
Gordon CRM uses a three-tier role system:
| Role | Badge | Permissions |
|---|---|---|
| Owner | Crown (amber) | Full access. Can invite members, change roles, transfer ownership, remove members, and manage all workspace settings. One owner per workspace. |
| Admin | Shield (emerald) | Can invite members, manage operational settings (sending domains, integrations), and access all CRM data. Cannot change roles or remove members. |
| Member | User (slate) | Standard CRM access — manage contacts, campaigns, tasks, deals, events, etc. Cannot modify workspace settings or team membership. |
Single-owner rule: Each workspace has exactly one owner at any time. Ownership is transferred, never promoted — see Ownership Transfer below.
Inviting Members
Owners and Admins can invite new users via the Invite Member button.
Invite Flow
- Click Invite Member to open the invite dialog
- Enter the recipient's email address
- Select a role — Admin or Member (Owner is not available for invitations)
- Click Send Invitation
The system:
- Checks if the email is already a workspace member → error: "This user is already a member of this workspace."
- Checks if a pending invitation already exists → error: "An invitation has already been sent to this email."
- Creates a row in
workspace_invitationswith a unique token - Sends an invitation email with an Accept Invitation button linking to
/api/invitations/accept?token=[token]
Invitation Acceptance
When the recipient clicks the link:
- Existing user — They are added to the workspace with the invited role
- New user — They are redirected to sign up, and the invitation is accepted automatically after signup
Pending Invitations
Owners and Admins see a Pending Invitations section below the members list. Each pending invitation shows the email, invited role, and a Cancel button to revoke the invitation.
Role Changes
The workspace Owner can change any other member's role via the action menu (⋮) on each member row. The available actions depend on the member's current role:
| Current Role | Available Actions |
|---|---|
| Admin | Make Member, Remove |
| Member | Make Admin, Remove |
| Owner | No actions (ownership is transferred, not demoted) |
Guards
- Self-change prevention — The owner cannot change their own role. The action menu is not shown for the owner's own row.
- Owner protection — The owner row has no action menu at all — it cannot be demoted or removed through the standard role change flow.
Ownership Transfer
Ownership transfer is a dedicated operation that atomically moves the owner role from one user to another.
How It Works
- The current owner opens the action menu (⋮) on any non-owner member
- Selects Transfer Ownership (Crown icon)
- Confirms the transfer
The system performs an atomic two-step operation:
- Demote all current owners to Admin (
UPDATE ... SET role = 'admin' WHERE role = 'owner') - Promote the target user to Owner (
UPDATE ... SET role = 'owner' WHERE user_id = target)
Important: This is a transfer, not a promotion. The previous owner becomes an Admin. There is never more than one owner at any time. This prevents ambiguity about who holds the final authority for the workspace.
Guard Rails
| Rule | Behavior |
|---|---|
| Self-transfer | Cannot change your own role (server-side guard) |
| Non-owner initiator | Only the current owner (or a super-admin) can initiate transfers |
| Atomic operation | Both the demote and promote happen in sequence — if either fails, the operation returns an error |
Removing Members
The workspace owner can remove any member (except themselves) via the action menu. A confirmation
dialog appears before removal. Removing a member deletes their workspace_users row — they lose
all access to the workspace immediately.
Super-Admin Bypass
Users with the is_super_admin flag on their user_profiles record bypass all workspace-level
role checks. The requireRole() function checks for super-admin status before evaluating
workspace membership.
This means a super-admin can:
- Transfer ownership on any workspace (even if they are not a member)
- Change roles for any member in any workspace
- Perform any owner-level operation without being the workspace owner
Super-admin status is set directly in the database (user_profiles.is_super_admin = true). There
is no UI to grant or revoke super-admin status — it is a platform-level flag managed by the
database administrator.
Data Model
workspace_users
| Column | Type | Description |
|---|---|---|
id | uuid | Primary key |
workspace_id | uuid | FK to workspaces.id |
user_id | uuid | FK to user_profiles.id (and auth.users.id) |
role | text | 'owner', 'admin', or 'member' |
created_at | timestamptz | When the user joined the workspace |
workspace_invitations
| Column | Type | Description |
|---|---|---|
id | uuid | Primary key |
workspace_id | uuid | FK to workspaces.id |
email | text | Invited email address |
role | text | 'admin' or 'member' (owners cannot be invited) |
token | uuid | Unique token used in the acceptance URL |
invited_by | uuid | FK to the user who sent the invitation |
accepted_at | timestamptz | NULL while pending, set when accepted |
created_at | timestamptz | When the invitation was created |