Role Renaming: Admin to Manager
Documentation for the role hierarchy change from Admin to Manager
Overview
The role hierarchy has been updated to use "Manager" instead of "Admin" for both team accounts and projects. This change provides clearer terminology and better reflects the responsibilities of this role level.
Role Hierarchy
Updated Hierarchy
Owner (Level 100) > Manager (Level 50) > Member (Level 25) > Readonly (Level 10)
Role Capabilities
Owner
- Full control over team/project
- Can delete team/project
- Can manage all members including other owners and managers
- Can transfer ownership
- Project Only: Only one owner per project (created automatically)
Manager (formerly Admin)
- Can manage team/project settings
- Can invite and remove members (except owners and other managers)
- Can update member roles (except owners and managers)
- Cannot: Delete team/project or modify owner/manager roles
- Team Accounts: Multiple managers allowed
- Projects: Multiple managers allowed
Member
- Standard access to team/project resources
- Can view and edit own tasks
- Limited permissions
Readonly
- View-only access
- Cannot modify anything
What Changed
Database
Enum Values
project_roleenum: Both 'admin' and 'manager' values exist- All existing 'admin' role data has been migrated to 'manager'
Tables Updated
-- Projects members UPDATE projects_members SET role = 'manager' WHERE role = 'admin'; -- Project invitations UPDATE project_invitations SET role = 'manager' WHERE role = 'admin';
Functions Updated
All RLS functions now check for 'manager' instead of 'admin':
is_project_admin()- checks for 'owner' or 'manager'is_team_admin()- checks for 'owner' or 'manager'check_project_permissions()- handles 'manager' permissionscan_manage_project_member()- manager-specific rulesupdate_project_member_role()- manager promotion restrictions
Code
UI Components
All role badges and selectors automatically display "Manager" based on translation keys:
// Translation key used in UI <Trans i18nKey="common:roles.manager.label" defaults="Manager" />
Translations
Already defined in apps/web/public/locales/en/common.json:
{
"roles": {
"owner": { "label": "Owner" },
"manager": { "label": "Manager" },
"member": { "label": "Member" }
}
}
Migration Details
Applied Migrations
File: 20251008030100_update_admin_to_manager_data.sql
This migration:
- Updates all
projects_membersrows with 'admin' role to 'manager' - Updates all
project_invitationsrows with 'admin' role to 'manager' - Safely handles cases where tables don't exist yet
- Provides feedback via RAISE NOTICE
Backward Compatibility
The migration maintains backward compatibility:
- Function names remain unchanged (e.g.,
is_project_admin()still exists) - Functions check for both 'owner' and 'manager' roles
- Existing code using these functions continues to work
Permissions Matrix
| Action | Owner | Manager | Member | Readonly |
|---|---|---|---|---|
| View team/project | ✅ | ✅ | ✅ | ✅ |
| Edit settings | ✅ | ✅ | ❌ | ❌ |
| Delete team/project | ✅ | ❌ | ❌ | ❌ |
| Invite members | ✅ | ✅ | ❌ | ❌ |
| Remove members | ✅ | ✅* | ❌ | ❌ |
| Change member roles | ✅ | ✅* | ❌ | ❌ |
| Manage managers | ✅ | ❌ | ❌ | ❌ |
| Transfer ownership | ✅ | ❌ | ❌ | ❌ |
* Manager restrictions:
- Cannot remove or modify owners or other managers
- Cannot promote members to manager or owner
Code Examples
Checking Manager Permission
// Server-side check
const { data: isAdmin } = await client.rpc('is_project_admin', {
p_project_id: projectId
});
// This function returns true for both owners and managers
if (isAdmin) {
// User can manage project settings
}
Inviting a Manager
// Client-side form
<FormField control={form.control} name="role" render={({ field })=> (
<Select value={field.value} onValueChange={field.onChange}>
<SelectItem value="manager">Manager</SelectItem>
<SelectItem value="member">Member</SelectItem>
</Select>
)}
/>
// Server action
export const inviteProjectMember = enhanceAction(
async (data) => {
// Role is validated as 'manager' or 'member'
await service.inviteMember(data);
},
{
schema: z.object({
role: z.enum(['owner', 'manager', 'member', 'readonly']),
email: z.string().email(),
}),
}
);
Role Badge Display
import { RoleBadge } from './role-badge';
// Automatically displays "Manager" from translations
<RoleBadge role="manager" />
Testing
Manual Testing Steps
- Create a test project
// Should create project with you as owner
await createProject({ name: 'Test Project' });
- Invite a manager
await inviteProjectMember({
email: 'manager@example.com',
role: 'manager'
});
- Verify manager permissions
- Manager can access project settings ✅
- Manager can invite new members ✅
- Manager cannot delete project ✅
- Manager cannot modify owner role ✅
- Check UI display
- Role badge shows "Manager" not "Admin" ✅
- Role selector shows "Manager" option ✅
E2E Tests
Existing E2E tests continue to work without modification as they use role-agnostic selectors:
await teamAccounts.updateMemberRole(memberEmail, 'owner');
Troubleshooting
Issue: Seeing "Admin" instead of "Manager"
Cause: Translation cache or old enum value
Solution:
# Regenerate types pnpm supabase:web:typegen # Clear browser cache # Hard refresh (Cmd+Shift+R on Mac)
Issue: Permission denied for manager
Cause: Function checking for old 'admin' role
Solution: Check which function is being used:
-- Should check for 'manager' not 'admin'
SELECT role FROM projects_members
WHERE role IN ('owner', 'manager'); -- ✅ Correct
Issue: Cannot create manager role
Cause: Enum value doesn't exist
Solution:
# Reset database and apply all migrations pnpm supabase:web:reset
Best Practices
When to Use Manager Role
✅ Use Manager for:
- Department leads
- Project coordinators
- Team facilitators
- Users who need to manage members but not critical settings
❌ Don't use Manager for:
- Financial decision makers (use Owner)
- Users who should view only (use Readonly)
- External contractors (use Member with specific permissions)
Security Considerations
- Limit managers carefully: Managers have significant permissions
- Regular audits: Review manager list periodically
- Principle of least privilege: Start with Member, promote as needed
- Document decisions: Note why someone was made a manager
Related Documentation
- Projects Overview - Complete projects feature guide
- Team Accounts Overview - Team management documentation
- Architecture & Database Schema - Database structure
Database Schema Reference
Projects Members Table
CREATE TABLE public.projects_members ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), project_id uuid REFERENCES public.projects(id) ON DELETE CASCADE, user_id uuid REFERENCES auth.users(id) ON DELETE CASCADE, role public.project_role NOT NULL DEFAULT 'member', -- role can be: 'owner', 'manager', 'member', 'readonly', etc. created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now() );
Project Role Enum
CREATE TYPE public.project_role AS ENUM ( 'owner', 'admin', -- Deprecated, use 'manager' 'manager', -- ✅ Use this 'member', 'readonly', 'investor', -- ... other custom roles );
Migration Changelog
| Date | Migration | Description |
|---|---|---|
| 2025-10-08 | 20251007040412 | Added 'manager' to project_role enum |
| 2025-10-08 | 20251008030100 | Migrated all 'admin' data to 'manager' |
Last Updated: October 8, 2025 Status: Active Breaking Changes: None (backward compatible)