# Workspace-Level Labels and Estimates Architecture in Plane: Backend and Frontend Deep Dive

> Explore the architecture of workspace-level labels and estimates in Plane. Discover how Django models, REST APIs, and React/MobX work together for efficient data management and UI rendering.

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

---

**Plane implements workspace-level labels and estimates through a layered architecture where Django models with workspace ForeignKeys persist data, REST APIs expose CRUD endpoints, and React/MobX stores cache and compute hierarchical views for the UI.**

Plane (makeplane/plane) treats labels and estimates as first-class workspace resources that can be shared across projects or scoped individually. This article examines the complete architecture for workspace-level labels and estimates, tracing the data flow from PostgreSQL tables through Django ORM models to the TypeScript service layer and MobX stores that power the React frontend.

## Backend Architecture for Workspace Resources

The backend implements workspace-level labels and estimates using Django models that inherit from `WorkspaceBaseModel`, enforcing data integrity through foreign key relationships and unique constraints at the database level.

### Label Models and Hierarchy

Labels in Plane support both workspace-wide and project-specific scopes, with optional parent-child relationships for categorization.

The `Label` model is defined in [`apps/api/plane/db/models/label.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/label.py). It inherits from `WorkspaceBaseModel`, which automatically links every label to a **workspace** via `workspace_id`. Labels can optionally belong to a specific **project** through the `project_id` field, or exist at the workspace level when `project_id` is null.

Key model features include:

- **Hierarchy support**: The `parent` field is a self-referencing foreign key that enables nested label categories
- **Custom ordering**: The `sort_order` field is auto-incremented on creation via the `save()` method to support drag-and-drop reordering
- **Uniqueness constraints**: The database enforces unique `name` values per workspace or per project when records are not soft-deleted

```python

# Conceptual model structure from apps/api/plane/db/models/label.py

class Label(WorkspaceBaseModel):
    name = models.CharField(max_length=255)
    color = models.CharField(max_length=255, blank=True)
    parent = models.ForeignKey("self", on_delete=models.CASCADE, null=True, blank=True)
    sort_order = models.FloatField(default=0)
    project = models.ForeignKey("db.Project", on_delete=models.CASCADE, null=True, blank=True)
    # workspace inherited from WorkspaceBaseModel

```

### Estimate Models and Point Systems

Estimates in Plane are technically owned by projects but exposed at the workspace level through aggregation endpoints. The architecture separates the estimate definition from its individual point values.

In [`apps/api/plane/db/models/estimate.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/estimate.py), the `Estimate` model links to a project via `project_id` and includes metadata fields:

- `type`: Either `categories` or `points` depending on the estimation method
- `last_used`: Boolean flag identifying the currently active estimate for the project

Individual estimation points are stored in the `EstimatePoint` model, which links to an `Estimate` via foreign key and stores the display value, numeric key, and optional description.

The `Project` model ([`apps/api/plane/db/models/project.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/project.py)) maintains an optional foreign key `estimate` pointing to the default estimate for that project, enabling quick lookup of the active estimation system.

```python

# From apps/api/plane/db/models/estimate.py

class EstimatePoint(models.Model):
    estimate = models.ForeignKey("db.Estimate", on_delete=models.CASCADE, related_name="points")
    key = models.IntegerField(default=0, validators=[MinValueValidator(0)])
    value = models.CharField(max_length=255)
    description = models.TextField(blank=True)

```

### REST API Endpoints

Plane exposes workspace-level labels and estimates through dedicated view modules that handle serialization and permission checks.

**Label Endpoints:**
- [`apps/api/plane/app/views/workspace/label.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/app/views/workspace/label.py): Handles `GET /api/workspaces/:slug/labels/` for workspace-wide label CRUD
- [`apps/api/plane/app/views/issue/label.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/app/views/issue/label.py): Manages `GET /api/workspaces/:slug/projects/:id/issue-labels/` for project-specific labels
- [`apps/api/plane/app/serializers/label.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/app/serializers/label.py): Serializes `Label` instances for API responses

**Estimate Endpoints:**
- [`apps/api/plane/app/views/workspace/estimate.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/app/views/workspace/estimate.py): Aggregates all estimates across a workspace via `GET /api/workspaces/:slug/estimates/`
- [`apps/api/plane/app/views/project/estimate.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/app/views/project/estimate.py): Handles project-specific estimate management
- [`apps/api/plane/app/serializers/estimate.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/app/serializers/estimate.py): Serializes both `Estimate` and `EstimatePoint` objects

## Frontend State Management

The frontend architecture uses MobX stores to cache workspace-level labels and estimates, providing computed getters that transform flat API responses into hierarchical structures suitable for UI components.

### LabelStore and Hierarchical Data

The `LabelStore` class in [`apps/web/core/store/label.store.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/label.store.ts) serves as the single source of truth for label data across the application.

The store maintains a `labelMap` (observable dictionary keyed by label ID) and provides computed getters that derive specific views:

- `workspaceLabels`: Returns all labels where `project_id` is null
- `projectLabels(projectId)`: Filters labels by specific project
- `projectLabelsTree`: Uses [`utils/buildTree.ts`](https://github.com/makeplane/plane/blob/main/utils/buildTree.ts) from `@plane/utils` to convert the flat label list into a hierarchical tree structure based on `parent` relationships

Data fetching flows through the service layer:

1. Components call `labelStore.fetchWorkspaceLabels(workspaceSlug)`
2. The store invokes `IssueLabelService.getWorkspaceIssueLabels(slug)` from [`apps/web/core/services/issue/issue_label.service.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/services/issue/issue_label.service.ts)
3. The service performs the HTTP GET to `/api/workspaces/:slug/labels/`
4. Response data populates the `labelMap`, triggering reactive UI updates

For drag-and-drop reordering, the store provides `updateLabelPosition()`, which calculates new `sort_order` values based on neighboring labels and persists changes via `IssueLabelService.patchIssueLabel()`.

### ProjectEstimateStore for Active Estimates

Estimates are managed by `ProjectEstimateStore` in [`apps/web/core/store/estimates/project-estimate.store.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/estimates/project-estimate.store.ts), which handles the complexity of tracking active versus archived estimates.

The store caches estimates in an `estimates` map and exposes computed properties:

- `currentActiveEstimate`: Returns the estimate where `last_used === true` for the current project
- `currentActiveEstimateId`: Returns the ID of the active estimate
- `archivedEstimateIds`: Lists estimates that are no longer active
- `areEstimateEnabledByProjectId`: Boolean check for estimate availability

The store wraps `EstimateService` (located at [`apps/web/core/services/estimate.service.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/services/estimate.service.ts)) to fetch data from `/api/workspaces/:slug/estimates/` and `/api/workspaces/:slug/projects/:id/estimates/`.

## Practical Implementation Examples

### Fetching Workspace-Level Labels

Components access workspace labels through the root store context, triggering fetches only when data is not cached:

```tsx
import { useRootStore } from "@/store/context";

const LabelDropdown = ({ workspaceSlug }: { workspaceSlug: string }) => {
  const { labelStore } = useRootStore();

  useEffect(() => {
    if (!labelStore.workspaceLabels) {
      labelStore.fetchWorkspaceLabels(workspaceSlug);
    }
  }, [workspaceSlug, labelStore]);

  const labels = labelStore.workspaceLabels ?? [];

  return (
    <Select>
      {labels.map((label) => (
        <Option key={label.id} value={label.id}>
          {label.name}
        </Option>
      ))}
    </Select>
  );
};

```

### Updating Label Hierarchy

Drag-and-drop reordering updates both parent relationships and sort order:

```typescript
await labelStore.updateLabelPosition(
  workspaceSlug,
  projectId,
  draggedLabelId,
  newParentId,   // null for top-level placement
  targetLabelId, // label to insert after
  false          // do not force drop at end
);

```

### Accessing Active Estimates

UI components determine which estimate system to display using computed store properties:

```typescript
const activeEstimate = projectEstimateStore.currentActiveEstimate;
if (activeEstimate) {
  console.log("Active estimate:", activeEstimate.name, activeEstimate.type);
  // Access points via activeEstimate.points
}

```

## Summary

- **Workspace-level labels** are stored in the `Label` model ([`apps/api/plane/db/models/label.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/label.py)) with optional project scoping and hierarchical parent relationships, exposed via [`apps/api/plane/app/views/workspace/label.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/app/views/workspace/label.py).
- **Estimates** are project-owned but workspace-accessible through aggregation endpoints in [`apps/api/plane/app/views/workspace/estimate.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/app/views/workspace/estimate.py), with points stored separately in the `EstimatePoint` model.
- **Frontend caching** uses MobX stores (`LabelStore` and `ProjectEstimateStore`) to maintain local state, compute hierarchical trees, and track active estimates via the `last_used` flag.
- **Service layer** abstraction in `IssueLabelService` and `EstimateService` decouples React components from direct API calls, enabling optimistic updates and error rollback.
- **Ordering and hierarchy** are handled through database-level `sort_order` fields and runtime tree-building utilities that transform flat API responses into nested UI structures.

## Frequently Asked Questions

### What is the difference between workspace-level and project-level labels in Plane?

Workspace-level labels have a null `project_id` and are available across all projects within that workspace, while project-level labels have a specific `project_id` foreign key and are only visible within that project. Both types store their workspace association via `WorkspaceBaseModel` inheritance, but the `LabelStore` frontend computes separate `workspaceLabels` and `projectLabels` getters to filter the cached `labelMap` accordingly.

### How does Plane handle label hierarchy and ordering?

Labels support arbitrary nesting through a self-referencing `parent` foreign key in the database model. The frontend uses [`utils/buildTree.ts`](https://github.com/makeplane/plane/blob/main/utils/buildTree.ts) to convert the flat list returned by the API into a hierarchical tree structure for display. Ordering is maintained via a `sort_order` float field; when users drag and drop labels, the `updateLabelPosition` method in `LabelStore` recalculates sort values based on neighboring labels and persists the change via PATCH requests to the label API.

### Can estimates be shared across multiple projects in a workspace?

No, estimates are owned by individual projects through a `project_id` foreign key in the `Estimate` model. However, the workspace-level API endpoint (`GET /api/workspaces/:slug/estimates/`) aggregates all estimates from all projects within the workspace, allowing users to browse and select from existing estimates when configuring new projects. The `Project` model tracks its currently selected default estimate via an `estimate` foreign key.

### How does the frontend determine which estimate is currently active?

The `ProjectEstimateStore` exposes a computed `currentActiveEstimate` getter that filters the cached estimates map to return the estimate where `last_used === true`. When a user selects a different estimate for a project, the backend updates the `last_used` flag on the `Estimate` model, and the store refreshes its local cache via `getProjectEstimates()`, triggering reactive updates in components like the estimate dropdown selector.