How to Manage User Roles and Permissions in Plane: A Complete Guide
Plane manages user roles through numeric enum hierarchies (admin=20, member=15, guest=5) enforced via a centralized permission store on the frontend and Django permission classes on the backend.
Managing user roles and permissions in Plane requires understanding its dual-layer security model that separates workspace-level access from project-level access. The open-source project management platform implements a type-safe permission system using TypeScript enums on the frontend and Python permission classes on the backend, ensuring consistent access control across both UI components and API endpoints.
Understanding Plane's Role Hierarchy
Plane defines role hierarchies through numeric enums where higher values indicate greater privileges. The system recognizes two distinct permission domains that work together to control access.
Workspace and Project Role Enums
The platform uses two primary enum definitions:
EUserWorkspaceRoles(defined inpackages/types/src/workspace.ts): Governs workspace-level access with valuesADMIN = 20,MEMBER = 15, andGUEST = 5EUserPermissions(defined inpackages/constants/src/user.ts): Controls project-level permissions using identical numeric values (ADMIN = 20,MEMBER = 15,GUEST = 5)
These numeric values enable simple comparison operations throughout the codebase. When a user holds workspace admin status, the BaseUserPermissionStore automatically elevates their project-level role to ADMIN as well, creating a cascading privilege model.
Frontend Permission Management
The frontend architecture centralizes permission logic in a dedicated store that aggregates user data from both workspace and project contexts.
The BaseUserPermissionStore
Located at apps/web/core/store/user/base-permissions.store.ts, the BaseUserPermissionStore serves as the single source of truth for permission state. It fetches role data through two primary methods:
fetchUserWorkspaceInfo: Retrieves workspace membership detailsfetchUserProjectInfo: Fetches project-specific role assignments
The store exposes computed helpers including getWorkspaceRoleByWorkspaceSlug and getProjectRoleByWorkspaceSlugAndProjectId to resolve current permission levels. The critical allowPermissions method accepts an array of permitted roles, a permission level enum (WORKSPACE or PROJECT), and optional identifiers to determine if the current user satisfies access requirements.
Role Utility Helpers
Utility functions in packages/utils/src/permission/role.ts handle role transformations:
getUserRole: Maps numeric enum values to human-readable strings (e.g.,20→"ADMIN")getHighestRole: Accepts a set of roles and returns the most privileged one
These utilities power UI components that need to display role labels or determine the highest access level across multiple memberships.
Backend Enforcement
On the server side, Plane implements Django permission classes to guard API endpoints. The ProjectMemberPermission class in apps/api/plane/app/permissions/project.py inspects the ProjectMember model—which stores roles as integers matching the frontend enums—to validate HTTP requests. This ensures that role-based restrictions apply at the API boundary, preventing unauthorized data access even if frontend checks are bypassed.
Practical Implementation Examples
Checking Permissions in React Components
To conditionally render UI elements based on project permissions, use the allowPermissions method from the root store:
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { useRootStore } from "@/store";
function DeleteButton({ workspaceSlug, projectId }: { workspaceSlug: string; projectId: string }) {
const { user } = useRootStore();
const canDelete = user.allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
EUserPermissionsLevel.PROJECT,
workspaceSlug,
projectId
);
return canDelete ? <button>Delete</button> : null;
}
This pattern checks if the current user holds either ADMIN (20) or MEMBER (15) status for the specified project before rendering the delete button.
Updating Member Roles via API
To programmatically change a user's role, invoke the project member service with the appropriate enum value:
import projectMemberService from "@/services/project/project-member.service";
import { EUserPermissions } from "@plane/constants";
// Promote a user to project admin
await projectMemberService.updateProjectMember(
workspaceSlug,
projectId,
userId,
{ role: EUserPermissions.ADMIN }
);
The service sends the numeric value 20 to the backend, where the ProjectMember model stores it in the database.
Displaying Role Labels
Convert numeric permissions to readable strings for the UI:
import { getUserRole } from "@plane/utils/permission/role";
import { EUserPermissions } from "@plane/constants";
const roleLabel = getUserRole(EUserPermissions.MEMBER); // Returns "MEMBER"
Summary
- Plane uses numeric enums (
EUserPermissionsandEUserWorkspaceRoles) with values 20 (admin), 15 (member), and 5 (guest) to represent role hierarchies - The
BaseUserPermissionStoreinapps/web/core/store/user/base-permissions.store.tscentralizes frontend permission logic and provides theallowPermissionsmethod for access checks - Workspace admins automatically receive project admin privileges, creating a cascading permission model
- Backend enforcement occurs through
ProjectMemberPermissioninapps/api/plane/app/permissions/project.py, which validates roles against theProjectMembermodel - Utility functions in
packages/utils/src/permission/role.tshandle conversion between numeric values and readable strings
Frequently Asked Questions
What are the different user roles in Plane?
Plane implements three role tiers: Admin (numeric value 20), Member (15), and Guest (5). These values apply to both workspace membership (EUserWorkspaceRoles) and project membership (EUserPermissions). Higher numeric values indicate greater privileges, enabling simple greater-than comparisons when checking permission levels.
How does Plane handle workspace admin versus project admin permissions?
According to the BaseUserPermissionStore implementation, workspace admins automatically inherit project-level admin rights. When the store's getProjectRole method detects that a user holds ADMIN status at the workspace level, it elevates their effective project role to ADMIN regardless of their specific project assignment. This eliminates the need for redundant role assignments while maintaining security boundaries.
Where does Plane enforce permission checks in the codebase?
Plane enforces permissions at two primary layers. The frontend uses the BaseUserPermissionStore (apps/web/core/store/user/base-permissions.store.ts) to conditionally render UI elements, while the backend relies on Django permission classes like ProjectMemberPermission (apps/api/plane/app/permissions/project.py) to protect API endpoints. The backend checks the ProjectMember model's integer role field against required levels for each HTTP request.
How do I check if a user has specific permissions in a Plane React component?
Import EUserPermissions and EUserPermissionsLevel from @plane/constants, then call the allowPermissions method from the root store. Pass an array of allowed roles (e.g., [EUserPermissions.ADMIN, EUserPermissions.MEMBER]), the permission level (EUserPermissionsLevel.PROJECT or WORKSPACE), and the relevant workspace slug and project ID. The method returns a boolean indicating whether the current user satisfies the requirements.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →