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_attimestamp field rather than hard deletion - Auditing fields including
created_atandupdated_attimestamps - Delete-aware unique constraints that include
deleted_at__isnull=Trueto 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, andbackground_colorfor display propertiesowner(ForeignKey →User) establishing the workspace creatorlogo_asset(ForeignKey →FileAsset) for branding- Reverse relationship
workspace_membersconnecting to users through theWorkspaceMemberthrough 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:
rolefield distinguishing admin, member, and guest permissions- JSON configuration fields:
view_props,default_props, andissue_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, andavatar_asset- Boolean flags for
is_superuserandis_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
Workspaceestablishing the tenant boundary identifierfield for project shorthand (e.g., "WEB")default_assigneeandproject_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 queriesTriageStateManager: 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:
parentfield (ForeignKey → self) supporting nested issues - State tracking: ForeignKey to
Statewith automatic default state assignment - Type classification: ForeignKey to
IssueType(bug, feature, etc.) - Estimation:
pointandestimate_pointfields for agile planning - Scheduling:
start_dateandtarget_datefields
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: LinksFileAssetrecords to issuesIssueActivity: Audit trail recording field changes and state transitionsIssueVoteandIssueReaction: 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=Falseby 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 sharedBaseModelproviding 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
Issueenable parent-child hierarchies, whileIssueRelationhandles 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →