# How Issues (Work Items) Are Structured in the Plane Database

> Discover how Plane structures work items as rich relational objects using the TBaseIssue interface. Learn about core attributes in issues_issue and auxiliary tables for relationships.

- Repository: [Plane/plane](https://github.com/makeplane/plane)
- Tags: internals
- Published: 2026-08-23

---

**Plane stores every work item as a rich relational object defined by the `TBaseIssue` interface in [`packages/types/src/issues/issue.ts`](https://github.com/makeplane/plane/blob/main/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`](https://github.com/makeplane/plane/blob/main/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 to `states_state.id` representing the current workflow state (line 51)
- **`priority`** – Enum value (`urgent`, `high`, `medium`, `low`, `none`) (line 52)
- **`project_id`** – Foreign key to `projects_project.id` indicating 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_epic`** and **`is_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_ids` array referencing `issue_labels.id` (line 53), implemented through the `issues_issue_labels` junction table
- **Assignees** – Array of user IDs (`assignee_ids`) linking to `users_user.id` via the `issues_issue_assignees` table (line 54)
- **Modules** – `module_ids` array connecting to `modules_module.id` through `issues_issue_modules` (line 64)
- **Cycles** – Single foreign key `cycle_id` pointing to `cycles_cycle.id` via `issues_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_count`** and **`link_count`** – Pre-computed metrics for UI rendering (lines 58-59)
- **`issue_attachments`**, **`issue_link`**, **`issue_relation`** – Collections of related objects stored in `issues_issue_attachments`, `issues_issue_links`, and `issues_issue_relations` respectively

### 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`](https://github.com/makeplane/plane/blob/main/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 action
- **`verb`** – Action type such as "created", "updated", or "commented"
- **`field`**, **`old_value`**, **`new_value`** – Delta information tracking specific field changes
- **`comment`** / **`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`](https://github.com/makeplane/plane/blob/main/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`](https://github.com/makeplane/plane/blob/main/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

```typescript
// 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

```typescript
// 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

```typescript
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 `TBaseIssue` interface in [`packages/types/src/issues/issue.ts`](https://github.com/makeplane/plane/blob/main/packages/types/src/issues/issue.ts) defines the core schema mapping to the `issues_issue` table with essential fields like `sequence_id`, `state_id`, and `project_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_id` enables nested work item trees with pre-computed `sub_issues_count` for performance.
- **Audit Trail** – The `IIssueActivity` interface captures every mutation with actor details and field deltas.
- **Type Safety** – Migration [`0074_deploy_board_and_project_issues.py`](https://github.com/makeplane/plane/blob/main/0074_deploy_board_and_project_issues.py) ensures 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`](https://github.com/makeplane/plane/blob/main/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.