# How Plane Implements Its Permission System: A Deep Dive into the Codebase

> Discover how Plane implements its type-safe, reactive permission system. Explore its codebase for details on enumerated roles, BaseUserPermissionStore, and USER_ALLOWED_PERMISSIONS map.

- Repository: [Plane/plane](https://github.com/makeplane/plane)
- Tags: deep-dive
- Published: 2026-08-23

---

**Plane implements a type-safe, reactive permission system using enumerated roles, a centralized `BaseUserPermissionStore`, and a declarative `USER_ALLOWED_PERMISSIONS` map that drives access control across both workspace and project scopes.**

The open-source project management tool [Plane](https://github.com/makeplane/plane) (makeplane/plane) handles complex authorization requirements through a layered architecture that separates role definitions from permission logic. Understanding how the Plane permission system works is essential for developers extending the platform or integrating custom access controls.

## Core Role Enumerations and Workspace Hierarchy

Plane organizes users into three distinct permission layers, each represented by TypeScript enums that enforce type safety throughout the application.

### Global and Scoped Role Definitions

The foundation rests on three enums defined in the `@plane/constants` and `@plane/types` packages. In [`packages/constants/src/user.ts`](https://github.com/makeplane/plane/blob/main/packages/constants/src/user.ts), the `EUserPermissions` enum defines global workspace-level capabilities:

```typescript
export enum EUserPermissions {
  GUEST = 5,
  MEMBER = 15,
  ADMIN = 20
}

```

For more granular control, [`packages/types/src/enums.ts`](https://github.com/makeplane/plane/blob/main/packages/types/src/enums.ts) provides scoped variants. `EUserWorkspaceRoles` handles workspace-specific assignments, while `EUserProjectRoles` governs individual project access. These numeric values enable simple comparison operations—higher values indicate greater privileges.

### Role Utility Helpers

The utility layer in [`packages/utils/src/permission/role.ts`](https://github.com/makeplane/plane/blob/main/packages/utils/src/permission/role.ts) provides two critical functions for role manipulation. The `getHighestRole` function resolves the most privileged role from an array of assignments:

```typescript
export const getHighestRole = <T extends TSupportedRole>(roles: T[]): T | undefined => {
  if (!roles?.length) return undefined;
  return roles.reduce((high, cur) => (cur > high ? cur : high));
};

```

This utility supports Plane's inheritance model where a user might hold different roles across multiple projects, requiring the system to determine effective permissions.

## The Central Permission Store Architecture

At the heart of the Plane permission system lies the `BaseUserPermissionStore` located in [`apps/web/core/store/user/base-permissions.store.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/user/base-permissions.store.ts). This MobX-powered store caches permission state and exposes the primary authorization API used throughout the frontend.

### BaseUserPermissionStore Implementation

The store maintains reactive state for `workspaceProjectsPermissions`, caching both workspace-wide and project-specific role assignments. It exposes two key lookup methods:

- `getWorkspaceRoleByWorkspaceSlug(workspaceSlug)` – Retrieves the user's workspace-level permission
- `getProjectRole(workspaceSlug, projectId?)` – Resolves the highest project-specific role, falling back to workspace permissions when necessary

The `allowPermissions` method serves as the universal gatekeeper:

```typescript
allowPermissions = (
  allowPermissions: ETempUserRole[],
  level: TUserPermissionsLevel,
  workspaceSlug: string,
  projectId?: string,
  onPermissionAllowed?: () => boolean
): boolean => {
  const role = level === EUserPermissionsLevel.WORKSPACE
    ? this.getWorkspaceRoleByWorkspaceSlug(workspaceSlug)
    : this.getProjectRole(workspaceSlug, projectId);

  const hasPermission = allowPermissions.includes(role as any);
  return hasPermission && (!onPermissionAllowed || onPermissionAllowed());
};

```

This method accepts a requested permission list, a level indicator (`WORKSPACE` or `PROJECT`), and an optional callback for custom validation logic.

### Reactive Integration with UserStore

A concrete `UserPermissionStore` extends the base class and integrates into the main user store at [`apps/web/core/store/user/index.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/user/index.ts). This architecture ensures that UI components automatically recompute access rights when underlying role data changes—for example, when a user receives a promotion in real-time.

## Declarative Permission Maps

Plane centralizes *what* each role may perform through the `USER_ALLOWED_PERMISSIONS` constant in [`packages/constants/src/user.ts`](https://github.com/makeplane/plane/blob/main/packages/constants/src/user.ts). This declarative map separates permission definitions from business logic, enabling changes to access rules without modifying checking code.

### USER_ALLOWED_PERMISSIONS Structure

The map uses a resource-action pattern where each feature (like labels or issues) specifies which roles may perform CRUD operations:

```typescript
export const USER_ALLOWED_PERMISSIONS: TUserAllowedPermissions = {
  PROJECT_LABELS: {
    read: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
    create: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
    update: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
    delete: [EUserPermissions.ADMIN],
  },
  // Additional resources: PROJECT_ISSUES, PROJECT_CYCLES, etc.
};

```

Because this configuration is data-driven, adding new resources requires only extending the constant object. The permission store references these arrays when evaluating access requests, comparing the user's resolved role against the allowed list.

## Permission Checking in Practice

Frontend components interact with the permission system through the store's reactive methods, enabling both conditional rendering and API call protection.

### UI Integration and Conditional Rendering

Components check permissions before rendering sensitive UI elements. For example, to conditionally show an "Add Label" button:

```typescript
const canCreateLabel = permissionStore.allowPermissions(
  USER_ALLOWED_PERMISSIONS.PROJECT_LABELS.create,
  EUserPermissionsLevel.PROJECT,
  workspaceSlug,
  projectId
);

```

The system evaluates the current user's project role against the creation whitelist. If the user holds either `ADMIN` or `MEMBER` status, the function returns true.

### Complete Implementation Examples

**Checking issue creation rights:**

```typescript
import { EUserPermissionsLevel, USER_ALLOWED_PERMISSIONS } from "@plane/constants";
import { UserPermissionStore } from "./store/user";

function canCreateIssue(
  permissionStore: UserPermissionStore,
  workspaceSlug: string,
  projectId: string
): boolean {
  return permissionStore.allowPermissions(
    USER_ALLOWED_PERMISSIONS.PROJECT_ISSUES.create,
    EUserPermissionsLevel.PROJECT,
    workspaceSlug,
    projectId
  );
}

```

**Resolving the highest role across team members:**

```typescript
import { getHighestRole, EUserPermissions } from "@plane/utils/permission";

const roles = members.map(m => m.role);
const highest = getHighestRole(roles); // Returns EUserPermissions.ADMIN if any admin exists

```

## Summary

- Plane's permission system uses **numeric enums** (`EUserPermissions`, `EUserWorkspaceRoles`, `EUserProjectRoles`) where higher values indicate greater access levels.
- The **`BaseUserPermissionStore`** in [`apps/web/core/store/user/base-permissions.store.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/user/base-permissions.store.ts) provides centralized, reactive permission state using MobX.
- **Role resolution** occurs through `getWorkspaceRoleByWorkspaceSlug` and `getProjectRole`, with automatic fallback from project to workspace level.
- The **`allowPermissions`** method validates access against `USER_ALLOWED_PERMISSIONS`, a declarative map separating configuration from logic.
- The **utility helpers** `getUserRole` and `getHighestRole` in [`packages/utils/src/permission/role.ts`](https://github.com/makeplane/plane/blob/main/packages/utils/src/permission/role.ts) handle role comparison and array reduction.
- All permission checks are **type-safe** and reactive, ensuring UI components update automatically when user roles change.

## Frequently Asked Questions

### How does Plane handle users with multiple roles across different projects?

Plane resolves effective permissions using the `getHighestRole` utility function from [`packages/utils/src/permission/role.ts`](https://github.com/makeplane/plane/blob/main/packages/utils/src/permission/role.ts). When a user belongs to multiple projects, the `BaseUserPermissionStore` evaluates each project assignment independently. For any specific action, the store compares the user's resolved role against the `USER_ALLOWED_PERMISSIONS` whitelist for that resource, using the highest privilege available in the current context.

### What is the difference between EUserPermissions and EUserProjectRoles?

`EUserPermissions` defined in [`packages/constants/src/user.ts`](https://github.com/makeplane/plane/blob/main/packages/constants/src/user.ts) represents global workspace-level capabilities with values GUEST (5), MEMBER (15), and ADMIN (20). `EUserProjectRoles` in [`packages/types/src/enums.ts`](https://github.com/makeplane/plane/blob/main/packages/types/src/enums.ts) provides project-specific role granularity. While they often share similar names, the project roles allow for fine-grained access control within individual projects, falling back to workspace permissions when no specific project assignment exists.

### Where does Plane store the active permission state for the current user?

The active permission state lives in the `BaseUserPermissionStore` located at [`apps/web/core/store/user/base-permissions.store.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/user/base-permissions.store.ts). This MobX store caches `workspaceProjectsPermissions` and provides reactive getters like `getWorkspaceRoleByWorkspaceSlug`. The store is instantiated within the main user store at [`apps/web/core/store/user/index.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/user/index.ts), making permission data available throughout the React component tree via the application's state management system.

### Can I modify Plane's permission rules without changing the core logic?

Yes. Plane uses a declarative permission map called `USER_ALLOWED_PERMISSIONS` in [`packages/constants/src/user.ts`](https://github.com/makeplane/plane/blob/main/packages/constants/src/user.ts). This constant object defines which roles can perform specific actions on resources like issues, labels, and cycles. By editing this configuration object—adding or removing roles from the `read`, `create`, `update`, or `delete` arrays—you can modify access control rules without touching the `allowPermissions` validation logic or any UI components.