# Plane Permission and Role System Architecture: A Deep Dive into RBAC Implementation

> Explore Plane's RBAC system architecture. Discover how integer roles, DRF permissions, and a TypeScript front-end store ensure robust access control. Learn more about Plane's permission system.

- Repository: [Plane/plane](https://github.com/makeplane/plane)
- Tags: architecture
- Published: 2026-06-18

---

**Plane implements a role-based access control (RBAC) system using integer-based roles (Admin=20, Member=15, Guest=5) stored in membership models, enforced through Django REST Framework permission classes and mirrored in a TypeScript front-end store.**

The open-source project management platform Plane (makeplane/plane) separates permission logic across its Django backend and Next.js frontend using a shared integer enum system. This architecture enables consistent access control for both workspace-level and project-level resources while maintaining a single source of truth in the database. Understanding this **Plane permission and role system architecture** is essential for customizing access controls or contributing to the codebase.

## Core Role Constants and Storage

Plane's RBAC system relies on three primary role levels represented as integer constants. These values are synchronized between the backend database and frontend application.

### Backend Role Definitions

In the Django models, roles are stored as `PositiveSmallIntegerField` with predefined choices. The `WorkspaceMember` model in [`apps/api/plane/db/models/workspace.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/workspace.py) and the `ProjectMember` model in [`apps/api/plane/db/models/project.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/project.py) both implement identical role storage:

- **Admin** = 20 (full administrative access)
- **Member** = 15 (standard collaborative access)
- **Guest** = 5 (read-only or limited access)

Both membership models include an `is_active` boolean flag that permission classes validate alongside role checks.

### Frontend Mirror Store

The TypeScript frontend maintains identical constants in [`apps/web/ce/store/user/permission.store.ts`](https://github.com/makeplane/plane/blob/main/apps/web/ce/store/user/permission.store.ts):

```typescript
export const Admin = 20;
export const Member = 15;
export const Guest = 5;

```

This mirroring allows the React frontend to make immediate UI decisions without waiting for backend round-trips, while ensuring consistency with the database schema.

## Backend Permission Architecture

The backend implements access control through Django REST Framework (DRF) `BasePermission` subclasses located in [`apps/api/plane/utils/permissions/workspace.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/utils/permissions/workspace.py).

### Primary Permission Classes

Plane defines granular permission classes for different access patterns:

- **WorkSpaceBasePermission**: Handles general workspace actions with method-specific role requirements. Grants POST to any authenticated user, SAFE_METHODS to active members, PUT/PATCH to Admin or Member roles, and DELETE exclusively to Admin.
- **WorkspaceOwnerPermission**: Restricts access strictly to Admin role holders (role = 20).
- **WorkSpaceAdminPermission**: Allows both Admin and Member roles (role ∈ [20, 15]) for edit operations.
- **WorkspaceEntityPermission**: Controls access to workspace entities like issues and cycles, requiring Admin or Member status for unsafe methods.
- **WorkspaceViewerPermission**: Grants read-only access to any active member regardless of role.

### Reusable Permission Helper

The `allow_permission` function in [`apps/api/plane/utils/permissions/base.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/utils/permissions/base.py) provides a generic mechanism for role validation:

```python
def allow_permission(allowed_roles, level="PROJECT", creator=False, model=None):
    # Convert enum members to integer values

    allowed_role_values = [
        role.value if isinstance(role, ROLE) else role for role in allowed_roles
    ]
    # Performs filtered query against membership tables

    return MembershipModel.objects.filter(
        member=request.user,
        role__in=allowed_role_values,
        is_active=True,
    ).exists()

```

This helper supports both workspace-level and project-level checks through the `level` parameter.

## Frontend Permission Store

The client-side permission system centers on a lightweight store that caches the current user's role from API endpoints like `/api/workspaces/:slug/members/`.

### Implementation Pattern

Components import role constants and validate against the cached role value:

```tsx
import { usePermission } from '@/store/user';
import { Admin, Member } from '@/store/user/permission.store';

function EditWorkspaceButton() {
  const { role } = usePermission(); // pulls role from the store

  if (![Admin, Member].includes(role)) {
    return null; // hide button for guests
  }

  return <button>Edit Workspace</button>;
}

```

This pattern ensures UI elements remain consistent with backend enforcement while providing immediate feedback to users.

## Permission Checking Flow

When a request reaches a DRF view, Plane evaluates permissions through the `has_permission` method chain. The system performs an ORM query against either `WorkspaceMember` or `ProjectMember` tables to verify both role eligibility and active status.

### API View Implementation

Developers apply permissions declaratively in view classes:

```python
from plane.utils.permissions.workspace import WorkspaceOwnerPermission
from rest_framework.views import APIView

class WorkspaceDeleteView(APIView):
    permission_classes = [WorkspaceOwnerPermission]

    def delete(self, request, *args, **kwargs):
        # Only workspace owners (role = Admin) reach this point

        # Deletion logic here

        pass

```

The permission class automatically handles the database lookup, checking that `request.user` has an active `WorkspaceMember` record with `role=20` before allowing the DELETE operation.

## Extending the System

Adding new roles or permission levels requires coordinated changes across the stack:

1. **Database Layer**: Update `ROLE_CHOICES` in [`apps/api/plane/db/models/workspace.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/workspace.py) or [`project.py`](https://github.com/makeplane/plane/blob/main/project.py) with new integer values
2. **Backend Logic**: Modify relevant permission classes in [`apps/api/plane/utils/permissions/workspace.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/utils/permissions/workspace.py) to reference the new role constants
3. **Frontend Sync**: Add corresponding constants to [`apps/web/ce/store/user/permission.store.ts`](https://github.com/makeplane/plane/blob/main/apps/web/ce/store/user/permission.store.ts)
4. **UI Updates**: Implement conditional rendering logic using the new role values

Project-level permissions follow an identical pattern to workspace permissions, with implementations located in [`apps/api/plane/utils/permissions/project.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/utils/permissions/project.py) querying the `ProjectMember` model.

## Summary

- Plane uses **integer-based RBAC** with Admin (20), Member (15), and Guest (5) roles stored in `WorkspaceMember` and `ProjectMember` models.
- **DRF permission classes** in [`apps/api/plane/utils/permissions/workspace.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/utils/permissions/workspace.py) enforce access rules through database validation of role and `is_active` status.
- The **frontend permission store** mirrors backend constants in [`apps/web/ce/store/user/permission.store.ts`](https://github.com/makeplane/plane/blob/main/apps/web/ce/store/user/permission.store.ts) for immediate UI state management.
- The **`allow_permission`** helper in [`base.py`](https://github.com/makeplane/plane/blob/main/base.py) provides reusable logic for custom permission implementations across both workspace and project contexts.

## Frequently Asked Questions

### How does Plane handle permission inheritance between workspace and project levels?

Plane maintains separate membership models for workspaces and projects. While a user might have Admin access at the workspace level, their project-level permissions are determined by separate `ProjectMember` records in [`apps/api/plane/db/models/project.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/project.py). The system does not automatically inherit workspace roles into projects; each membership is validated independently through project-specific permission classes in [`apps/api/plane/utils/permissions/project.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/utils/permissions/project.py).

### Can custom roles be added to Plane's permission system without modifying core code?

Yes, custom roles require extending the `ROLE_CHOICES` enum in the Django models and adding corresponding integer constants to the TypeScript store. You must also create new permission classes or extend existing ones in [`apps/api/plane/utils/permissions/workspace.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/utils/permissions/workspace.py) to recognize the new role values. The modular architecture allows these extensions without altering the base permission logic.

### What happens when a workspace member's `is_active` flag is set to false?

Permission classes check both role eligibility and the `is_active` boolean flag. When `is_active=False`, the user effectively loses all access regardless of their role value. The `allow_permission` helper and individual permission classes like `WorkspaceViewerPermission` explicitly filter for `is_active=True`, ensuring deactivated members cannot read or modify workspace resources.

### Where does the frontend retrieve the current user's role information?

The permission store fetches role data from the API endpoint `/api/workspaces/:slug/members/` (or project-specific equivalents) and caches it locally. This cached value persists in the TypeScript store located at [`apps/web/ce/store/user/permission.store.ts`](https://github.com/makeplane/plane/blob/main/apps/web/ce/store/user/permission.store.ts), allowing components to reference `usePermission()` hooks for immediate access control decisions without repeated network requests.