Plane Django Database Models: Complete Schema and Relationship Guide

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 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. 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 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 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, 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 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 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.

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) 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, 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


# 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. 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) is the top-level tenant container containing multiple Project instances (defined in 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, 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, 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.

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 →