How Issues (Work Items) Are Structured in the Plane Database
Plane stores every work item as a rich relational object defined by the TBaseIssue interface in packages/types/src/issues/issue.ts, with core attributes stored in the issues_issue table and auxiliary tables handling many-to-many relationships like labels, assignees, and modules.
Understanding how issues are structured in the Plane database is essential for developers integrating with the open-source project management platform. The schema follows a normalized relational design where the base issue record maintains essential fields, while separate junction tables manage complex relationships. This architecture supports hierarchical work items, sprint cycles, modular grouping, and complete activity auditing.
Core Issue Schema – The TBaseIssue Interface
The foundation of how issues are structured in Plane resides in the TBaseIssue type definition located in packages/types/src/issues/issue.ts. This interface maps directly to columns in the issues_issue table and contains the essential atomic fields every work item requires.
Key properties include:
id– UUID primary key for the issue (line 45)sequence_id– Integer used for ordering within a project (line 46)name– The issue title displayed in the UI (line 48)sort_order– UI-side weight for manual ordering (line 49)state_id– Foreign key tostates_state.idrepresenting the current workflow state (line 51)priority– Enum value (urgent,high,medium,low,none) (line 52)project_id– Foreign key toprojects_project.idindicating ownership (line 61)
The interface also captures scheduling and lifecycle metadata:
start_date,target_date,completed_at,archived_at– Optional datetime fields for project planning (lines 69-72)is_draft– Boolean flag indicating visibility status before publication (line 77)is_epicandis_intake– Optional booleans for higher-level categorization (lines 78-79)created_by/updated_by– User IDs tracking the last mutation (lines 74-75)
Extended Issue Model and Relationships
The TIssue type extends TBaseIssue with optional richer data used by the frontend, including description_html for rich-text content and convenience fields like state__group for logical state bucketing.
Many-to-Many Relationships
Plane’s database structure uses junction tables to manage complex associations without denormalizing the core issue table:
- Labels – Stored via
label_idsarray referencingissue_labels.id(line 53), implemented through theissues_issue_labelsjunction table - Assignees – Array of user IDs (
assignee_ids) linking tousers_user.idvia theissues_issue_assigneestable (line 54) - Modules –
module_idsarray connecting tomodules_module.idthroughissues_issue_modules(line 64) - Cycles – Single foreign key
cycle_idpointing tocycles_cycle.idviaissues_issue_cycles(line 63)
Nested Objects and Attachments
For performance optimization, Plane maintains pre-computed counts and nested object arrays:
sub_issues_count– Cached count of child issues for hierarchical views (line 57)attachment_countandlink_count– Pre-computed metrics for UI rendering (lines 58-59)issue_attachments,issue_link,issue_relation– Collections of related objects stored inissues_issue_attachments,issues_issue_links, andissues_issue_relationsrespectively
Parent-Child Hierarchies
The schema supports recursive work item structures through the parent_id field (line 62), a self-referencing foreign key to issues_issue.id. The API returns a shallow parent object containing base fields for quick access without requiring additional queries.
Activity and Audit Trail
Every mutation creates an immutable record defined by the IIssueActivity interface in packages/types/src/issues.ts. This activity log enables full audit trails and history views throughout the application.
Key activity fields include:
actor/actor_detail– The user performing the actionverb– Action type such as "created", "updated", or "commented"field,old_value,new_value– Delta information tracking specific field changescomment/comment_html– Optional payload for discussion threads
Database Implementation Details
The concrete database schema is established in migration 0074_deploy_board_and_project_issues.py located at apps/api/plane/db/migrations/. This migration introduces the project_id, cycle_id, and module_ids columns that support the relational structure described in the TypeScript interfaces.
The Django ORM layer (implemented in apps/api/plane/models/issue.py) mirrors these type definitions, ensuring type safety between the PostgreSQL backend and the TypeScript frontend.
Practical Code Examples
When working with the Plane API, developers interact with these structures through strongly-typed payloads:
Creating a New Issue
// Payload sent to POST /api/v1/projects/{project_id}/issues/
const newIssue: Partial<TIssue> = {
name: "Implement OAuth login",
description_html: "<p>We need OAuth for SSO integration.</p>",
project_id: "proj_123",
priority: "high",
assignee_ids: ["user_45", "user_67"],
label_ids: ["label_bug", "label_frontend"],
state_id: "state_todo",
type_id: "issue_type_feature"
};
Bulk Updating Issues
// Moving multiple issues to a new sprint cycle
const bulkPayload = {
issue_ids: ["issue_1", "issue_2", "issue_3"],
properties: {
cycle_id: "cycle_2024_q3",
state_id: "state_in_progress"
}
};
Parsing Sub-Issue Responses
function getSubIssues(response: ISubIssueResponse): TIssue[] {
// response.state_distribution provides quick stats
console.log(response.state_distribution);
// response.sub_issues contains full TIssue objects
return response.sub_issues;
}
Summary
- Base Structure – The
TBaseIssueinterface inpackages/types/src/issues/issue.tsdefines the core schema mapping to theissues_issuetable with essential fields likesequence_id,state_id, andproject_id. - Relational Design – Junction tables (
issues_issue_labels,issues_issue_assignees,issues_issue_modules) handle many-to-many relationships while keeping the base table normalized. - Hierarchy Support – Self-referencing
parent_idenables nested work item trees with pre-computedsub_issues_countfor performance. - Audit Trail – The
IIssueActivityinterface captures every mutation with actor details and field deltas. - Type Safety – Migration
0074_deploy_board_and_project_issues.pyensures the PostgreSQL schema aligns with the TypeScript type definitions.
Frequently Asked Questions
What table stores the primary issue data in Plane?
The primary issue data resides in the issues_issue table, mapped by the TBaseIssue TypeScript interface. This table contains core fields including id, name, sequence_id, state_id, and project_id, while auxiliary tables handle relationships like labels and assignees.
How does Plane handle issue hierarchies and sub-tasks?
Plane implements hierarchies through a self-referencing foreign key. The parent_id field in TBaseIssue links to another record in issues_issue, while the sub_issues_count column maintains a cached count of children for quick UI rendering without recursive queries.
Where is the activity history for issues stored?
Activity history is defined by the IIssueActivity interface and persisted in the issues_issue_activity table. Each record captures the actor, verb (action type), and optional delta fields (old_value, new_value) to provide complete audit trails for compliance and debugging.
What migration created the current issue schema structure?
The migration 0074_deploy_board_and_project_issues.py established the current schema, adding critical columns like project_id, cycle_id, and module_ids that support Plane’s board views and sprint planning features.
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 →