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 in packages/types/src/workspace.ts): Governs workspace-level access with values ADMIN = 20, MEMBER = 15, and GUEST = 5
  • EUserPermissions (defined in packages/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 details
  • fetchUserProjectInfo: 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 (EUserPermissions and EUserWorkspaceRoles) with values 20 (admin), 15 (member), and 5 (guest) to represent role hierarchies
  • The BaseUserPermissionStore in apps/web/core/store/user/base-permissions.store.ts centralizes frontend permission logic and provides the allowPermissions method for access checks
  • Workspace admins automatically receive project admin privileges, creating a cascading permission model
  • Backend enforcement occurs through ProjectMemberPermission in apps/api/plane/app/permissions/project.py, which validates roles against the ProjectMember model
  • Utility functions in packages/utils/src/permission/role.ts handle 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →