# Plane Django Database Models: Complete Schema and Relationship Guide

> Explore Plane's Django database models, schema, and relationships. Understand multi-tenant project management, soft-delete models, UUID keys, and complex many-to-many connections.

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

---

**Plane's Django backend implements a multi-tenant project management schema with soft-delete aware models, UUID primary keys, and complex many-to-many relationships connecting workspaces, projects, and issues.**

The open-source project management platform [makeplane/plane](https://github.com/makeplane/plane) organizes its data layer under `apps/api/plane/db/models/`, where every entity inherits from a custom `BaseModel` that provides auditing fields, soft deletion capabilities, and UUID-based primary keys. This architecture supports tenant-isolated workspaces while maintaining referential integrity across complex issue tracking relationships.

## Core Architecture and BaseModel

All database models in Plane extend the abstract `BaseModel` defined in [`apps/api/plane/db/models/base.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/base.py). This base class provides:

- **UUID primary keys** for distributed-safe identifier generation
- **Soft deletion** via a `deleted_at` timestamp field rather than hard deletion
- **Auditing fields** including `created_at` and `updated_at` timestamps
- **Delete-aware unique constraints** that include `deleted_at__isnull=True` to prevent unique constraint violations on soft-deleted rows while preserving data integrity

When a record is soft-deleted, Plane appends a timestamp to slug fields (like in `Workspace`) to allow reuse of the original identifier by new records.

## Workspace and User Management Models

### Workspace Model

The **`Workspace`** model in [`apps/api/plane/db/models/workspace.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/workspace.py) serves as the top-level tenant container. Key fields include:

- `name`, `slug`, and `background_color` for display properties
- `owner` (ForeignKey → `User`) establishing the workspace creator
- `logo_asset` (ForeignKey → `FileAsset`) for branding
- Reverse relationship `workspace_members` connecting to users through the `WorkspaceMember` through model

The slug field maintains uniqueness at the database level, with soft-deleted workspaces receiving timestamp suffixes to free up the slug for reuse.

### Membership and Invitations

**`WorkspaceMember`** enforces workspace access control with a unique constraint on `(workspace, member)` while ignoring soft-deleted rows. It stores:

- `role` field distinguishing admin, member, and guest permissions
- JSON configuration fields: `view_props`, `default_props`, and `issue_props`

**`WorkspaceMemberInvite`** handles pending invitations with a soft-delete-aware unique constraint on `(email, workspace)`, storing acceptance status and verification tokens.

### User Authentication and Profiles

The **`User`** model in [`apps/api/plane/db/models/user.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/user.py) manages authentication with:

- `email` (normalized to lowercase on save), `username`, and `avatar_asset`
- Boolean flags for `is_superuser` and `is_staff`
- Automatic generation of random display names when missing

**`Profile`** maintains a One-to-One relationship with `User`, storing JSON fields for theme preferences, onboarding state, and product tour progress.

**`Account`** links external OAuth providers (Google, GitHub, GitLab) to users via `provider` and `provider_account_id` fields with unique constraints.

## Project Structure and Configuration

### Project Model

Defined in [`apps/api/plane/db/models/project.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/project.py), the **`Project`** model represents work containers within a workspace:

- ForeignKey to `Workspace` establishing the tenant boundary
- `identifier` field for project shorthand (e.g., "WEB")
- `default_assignee` and `project_lead` (ForeignKeys → `User`)
- `default_state` (ForeignKey → `State`) for new issues

Projects automatically inherit the workspace timezone on creation unless explicitly overridden.

### Project Membership

**`ProjectMember`** manages user access at the project level with role-based permissions and JSON preference storage. Upon creation, Plane automatically generates a corresponding `ProjectUserProperty` record to store per-project ordering configurations.

**`ProjectMemberInvite`** follows the same pattern as workspace invites, with unique constraints preventing duplicate pending invitations.

### State Workflow Management

The **`State`** model in [`apps/api/plane/db/models/state.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/state.py) defines workflow stages (Backlog, Todo, Done). It implements two custom managers:

- **`StateManager`**: Excludes triage states from default queries
- **`TriageStateManager`**: Specifically handles triage workflow states

States include `sequence` ordering, `color` hex codes, and `group` classifications (Backlog, Unstarted, Started, Completed, Cancelled).

## Issue Tracking Entity Models

### The Issue Model

The **`Issue`** model in [`apps/api/plane/db/models/issue.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/issue.py) forms the core work item with rich relationships:

- **Self-referential hierarchy**: `parent` field (ForeignKey → self) supporting nested issues
- **State tracking**: ForeignKey to `State` with automatic default state assignment
- **Type classification**: ForeignKey to `IssueType` (bug, feature, etc.)
- **Estimation**: `point` and `estimate_point` fields for agile planning
- **Scheduling**: `start_date` and `target_date` fields

Issues implement soft deletion, sequence handling for ordering, and lock-based concurrency control.

### Issue Relations and Hierarchies

**`IssueRelation`** creates bidirectional relationships between issues (blocks, duplicates, relates to) using `relation_type` choices defined in `IssueRelationChoices`. The model stores both the source `issue` and `related_issue` as ForeignKeys to the Issue model, enabling graph traversal of dependencies.

### Issue Assignees and Labels

Rather than standard ManyToManyFields, Plane uses explicit through models:

**`IssueAssignee`** (ForeignKey to `Issue` and ForeignKey to `User`) with unique constraints preventing duplicate assignments.

**`IssueLabel`** (ForeignKey to `Issue` and ForeignKey to `Label`) connects issues to workspace-scoped labels defined in [`apps/api/plane/db/models/label.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/label.py).

### Comments, Attachments, and Activity

**`IssueComment`** links to issues with an `actor` ForeignKey to `User` and a One-to-One relationship with the **`Description`** model (defined in [`apps/api/plane/db/models/description.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/description.py)) for rich-text content. The model handles HTML sanitization and automatically creates or updates the linked Description record.

Auxiliary models include:
- **`IssueAttachment`**: Links `FileAsset` records to issues
- **`IssueActivity`**: Audit trail recording field changes and state transitions
- **`IssueVote`** and **`IssueReaction`**: User feedback mechanisms with emoji support

## Soft Delete Implementation and Constraints

Plane's soft delete architecture ensures data integrity without sacrificing unique constraints. In [`apps/api/plane/db/models/base.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/base.py), the `BaseModel` establishes that all unique constraints must include `deleted_at__isnull=True`.

This pattern allows:
- **Reusable identifiers**: Deleted workspace slugs can be reused by appending timestamps
- **Clean queries**: Active records exclude `deleted_at__isnull=False` by default
- **Referential safety**: Foreign keys remain valid to soft-deleted records while preventing duplicate active records

## Practical Usage Examples

```python

# Create workspace and owner

owner = User.objects.create_user(email="alice@example.com", username="alice")
workspace = Workspace.objects.create(
    name="Acme Corp",
    owner=owner,
    slug="acme-corp",
)

# Add workspace member

member = User.objects.create_user(email="bob@example.com", username="bob")
WorkspaceMember.objects.create(
    workspace=workspace,
    member=member,
    role=20,  # Admin role

)

# Create project with identifier

project = Project.objects.create(
    workspace=workspace,
    name="Website Redesign",
    identifier="WEB",
)

# Define workflow state

backlog_state = State.objects.create(
    project=project,
    name="Backlog",
    color="#60646C",
    group="backlog",
    default=True,
)

# Create issue with assignees and labels

issue = Issue.objects.create(
    project=project,
    name="Landing page mockup",
    state=backlog_state,
)
IssueAssignee.objects.create(issue=issue, assignee=member)

label = Label.objects.create(workspace=workspace, name="Design")
IssueLabel.objects.create(issue=issue, label=label)

# Query with prefetching

issues = Issue.objects.filter(project=project).prefetch_related("assignees")
for i in issues:
    print(f"{i.name}: {[u.email for u in i.assignees.all()]}")

```

## Summary

- **Plane's Django models** reside in `apps/api/plane/db/models/` and inherit from a shared `BaseModel` providing UUID primary keys and soft deletion
- **Workspace** serves as the top-level tenant boundary, with **Project** containers holding **Issue** work items
- **Many-to-many relationships** use explicit through models (`IssueAssignee`, `IssueLabel`) with unique constraints preventing duplicates
- **Self-referential ForeignKeys** on `Issue` enable parent-child hierarchies, while `IssueRelation` handles bidirectional dependencies
- **Soft deletion** preserves referential integrity through timestamp-based unique constraints and allows identifier reuse

## Frequently Asked Questions

### How does Plane implement soft deletion in Django models?

Plane implements soft deletion through a `deleted_at` DateTimeField defined in [`apps/api/plane/db/models/base.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/base.py). Rather than calling `delete()`, models set this timestamp, and unique constraints include `deleted_at__isnull=True` to prevent conflicts between active and soft-deleted records. Workspace slugs receive timestamp suffixes upon deletion to free the identifier for reuse.

### What is the relationship between Workspace and Project in Plane?

**Workspace** (defined in [`apps/api/plane/db/models/workspace.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/workspace.py)) is the top-level tenant container containing multiple **Project** instances (defined in [`apps/api/plane/db/models/project.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/project.py)). Projects inherit the workspace's timezone by default and maintain ForeignKey relationships to their parent workspace. This one-to-many relationship supports multi-tenant isolation where projects cannot cross workspace boundaries.

### How are issue assignees handled in the database schema?

Issue assignees use an explicit through model called **`IssueAssignee`** in [`apps/api/plane/db/models/issue.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/issue.py), containing ForeignKeys to both `Issue` and `User` with a unique constraint preventing duplicate assignments. The `Issue` model exposes this as a ManyToManyField with `through=IssueAssignee`, allowing queries like `issue.assignees.all()` while maintaining the ability to store additional metadata on the assignment relationship.

### Where are user authentication and OAuth accounts stored?

User authentication principals reside in the **`User`** model within [`apps/api/plane/db/models/user.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/user.py), storing credentials, email (normalized to lowercase), and avatar references. External OAuth accounts (Google, GitHub, GitLab) are stored in the **`Account`** model linked via ForeignKey to `User`, with unique constraints on the combination of `provider` and `provider_account_id` to prevent duplicate social account bindings.