# How Workspace Views Support Custom Filters, Sorting, and Saved Filter Persistence in Plane

> Learn how Plane's workspace views empower custom filters, sorting, and saved filter persistence. Explore Plane's three-layer data model for advanced query logic and UI state restoration.

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

---

**Plane's workspace views persist user-specific filtering preferences through a three-layer data model comprising rich filter expressions, display configuration options, and view properties, enabling both complex query logic and UI state restoration across sessions.**

Workspace views in Plane (makeplane/plane) serve as the central mechanism for saving and restoring personalized issue browsing configurations. Each view encapsulates filter logic, sorting preferences, and display settings, allowing teams to create persistent, shareable perspectives on their work items. The implementation relies on a typed TypeScript interface and a MobX-based store architecture that synchronizes state between the frontend and REST API.

## The Workspace View Data Model

According to the Plane source code, every workspace view adheres to the `IWorkspaceView` interface defined in [`packages/types/src/workspace-views.ts`](https://github.com/makeplane/plane/blob/main/packages/types/src/workspace-views.ts). This structure stores three distinct categories of persistence data:

- **`rich_filters`**: A tree-based filter expression (`TWorkItemFilterExpression`) supporting nested logical operators and conditions on fields like assignee, state, and label.
- **`display_filters`**: UI-level configuration including layout mode (`list` or `kanban`), grouping criteria, ordering fields, and sort direction.
- **`display_properties`**: Additional view settings such as visible columns and estimate visibility.

This separation allows the system to distinguish between query logic (what issues to show) and presentation logic (how to show them).

## Building Complex Filter Expressions

Custom filtering in Plane utilizes **rich filters** to construct arbitrarily complex query expressions. When users build filters in the UI, the `WorkspaceIssuesFilter` store in [`apps/web/core/store/issue/workspace/filter.store.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/issue/workspace/filter.store.ts) manages the state updates.

The store exposes the `updateFilterExpression` method to modify the filter tree:

```typescript
// Inside WorkspaceIssuesFilter.updateFilterExpression
runInAction(() => {
  set(this.filters, [viewId, "richFilters"], filters);
});

```

After updating the expression, the store triggers an issue list reload. The `rich_filters` property supports nested logical operators (AND/OR), comparison operators, and field-specific constraints, enabling queries like "high priority bugs assigned to me or my team."

## Configuring Sorting and Layout

Sorting preferences and view layouts reside in the `display_filters` object. Plane defines available sorting keys in [`packages/constants/src/views.ts`](https://github.com/makeplane/plane/blob/main/packages/constants/src/views.ts), including:

- `name`
- `created_at`
- `updated_at`

Sort direction accepts `asc` or `desc` values.

When users modify display options, the `WorkspaceIssuesFilter` processes updates through the `updateFilters` method:

```typescript
// Inside WorkspaceIssuesFilter.updateFilters – DISPLAY_FILTERS case
const updatedDisplayFilters = filters as IIssueDisplayFilterOptions;
_filters.displayFilters = { ..._filters.displayFilters, ...updatedDisplayFilters };
runInAction(() => {
  Object.keys(updatedDisplayFilters).forEach((_key) => {
    set(this.filters, [viewId, "displayFilters", _key],
        updatedDisplayFilters[_key as keyof IIssueDisplayFilterOptions]);
  });
});

```

The system differentiates between **static views** (such as "All Issues" or "Assigned to Me") and **custom views**. For static views identified in `STATIC_VIEW_TYPES` ([`packages/types/src/workspace-views.ts`](https://github.com/makeplane/plane/blob/main/packages/types/src/workspace-views.ts)), changes persist locally via `handleIssuesLocalFilters.set`. For custom views, the system routes changes through the API service.

## Persisting Views to the Backend

Long-term persistence occurs through the `WorkspaceViewService` class in [`packages/services/src/workspace/view.service.ts`](https://github.com/makeplane/plane/blob/main/packages/services/src/workspace/view.service.ts). This service implements standard CRUD operations against the Plane REST API:

| Method | Endpoint | Purpose |
|--------|----------|---------|
| `create` | `POST /api/workspaces/:slug/views/` | Saves a new view with filters and sorting |
| `update` | `PATCH /api/workspaces/:slug/views/:id/` | Updates existing view configuration |
| `list` | `GET /api/workspaces/:slug/views/` | Retrieves all workspace views |
| `retrieve` | `GET /api/workspaces/:slug/views/:id/` | Fetches specific view definition |
| `destroy` | `DELETE /api/workspaces/:slug/views/:id/` | Removes a saved view |

When users save a custom view, the frontend invokes `viewService.update()` with the complete state:

```typescript
await viewService.update(workspaceSlug, viewId, {
  rich_filters: currentRichFilters,
  display_filters: currentDisplayFilters,
  display_properties: currentDisplayProperties,
});

```

The backend stores the JSON representation of the `IWorkspaceView` interface, ensuring that reopening the workspace restores the exact filter expression, layout, and sort order.

## Static vs. Custom View Persistence Behavior

Plane handles static and custom views differently to optimize performance. Static views—identified by `STATIC_VIEW_TYPES` including `"all-issues"`, `"assigned"`, `"created"`, and `"subscribed"`—skip network persistence and store filters locally.

The `fetchFilters` method in [`filter.store.ts`](https://github.com/makeplane/plane/blob/main/filter.store.ts) implements this logic:

```typescript
if (STATIC_VIEW_TYPES.includes(viewId) === false) {
  const _filters = await this.issueFilterService.getViewDetails(workspaceSlug, viewId);
  // Populate rich/display filters from server response
}

```

Custom views follow the full HTTP persistence path, guaranteeing that complex filter trees and sorting preferences survive across sessions and devices.

## Implementation Examples

### Creating a New Workspace View

The following example demonstrates saving a new view with complex filtering logic:

```typescript
import { WorkspaceViewService } from "@plane/services";

const viewService = new WorkspaceViewService();

const newView = {
  name: "My Critical Bugs",
  description: "Bugs I own with high priority",
  rich_filters: {
    operator: "AND",
    children: [
      { field: "state", value: "bug" },
      { field: "priority", operator: "IN", value: ["high", "critical"] },
      { field: "assignee", value: "currentUser" }
    ]
  },
  display_filters: {
    layout: "list",
    order_by: "created_at",
    sort: "desc"
  },
  display_properties: { show_estimates: true }
};

await viewService.create("my-workspace-slug", newView);

```

Source: `WorkspaceViewService.create` in [`packages/services/src/workspace/view.service.ts`](https://github.com/makeplane/plane/blob/main/packages/services/src/workspace/view.service.ts)

### Updating Sort Order for Existing Views

To modify sorting preferences for an existing view:

```typescript
await viewService.update("my-workspace-slug", viewId, {
  display_filters: {
    order_by: "updated_at",
    sort: "asc"
  }
});

```

Source: `WorkspaceViewService.update` in [`packages/services/src/workspace/view.service.ts`](https://github.com/makeplane/plane/blob/main/packages/services/src/workspace/view.service.ts)

### Accessing Filter State in the Store

The MobX store maintains observable filter state accessible after fetching:

```typescript
// Inside WorkspaceIssuesFilter.fetchFilters
await this.fetchFilters("my-workspace-slug", "custom-view-id");

// After fetch, the store's observable filters contains:
// {
//   "custom-view-id": {
//     richFilters: { ... },
//     displayFilters: { layout: "list", order_by: "created_at", sort: "desc" },
//     displayProperties: { ... },
//     kanbanFilters: { ... }
//   }
// }

```

Source: [`apps/web/core/store/issue/workspace/filter.store.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/issue/workspace/filter.store.ts)

## Summary

- **Workspace views** in Plane persist through three structured properties: `rich_filters` for query logic, `display_filters` for sorting and layout, and `display_properties` for UI configuration.
- **Rich filters** support complex nested expressions via `TWorkItemFilterExpression`, managed by the `WorkspaceIssuesFilter` store in [`apps/web/core/store/issue/workspace/filter.store.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/issue/workspace/filter.store.ts).
- **Display filters** control layout modes, grouping, and sorting keys defined in [`packages/constants/src/views.ts`](https://github.com/makeplane/plane/blob/main/packages/constants/src/views.ts), with state updates handled through MobX actions.
- **Persistence** occurs via `WorkspaceViewService` in [`packages/services/src/workspace/view.service.ts`](https://github.com/makeplane/plane/blob/main/packages/services/src/workspace/view.service.ts), which communicates with REST endpoints to save custom view configurations.
- **Static views** (All Issues, Assigned to Me, etc.) store preferences locally, while **custom views** synchronize to the backend for multi-device accessibility.

## Frequently Asked Questions

### How does Plane store complex filter conditions in workspace views?

Plane stores complex conditions in the `rich_filters` property as a `TWorkItemFilterExpression` tree structure. This JSON object supports nested logical operators like AND and OR, along with field-specific comparisons for assignees, labels, states, and priorities. The expression tree lives in the `IWorkspaceView` interface defined in [`packages/types/src/workspace-views.ts`](https://github.com/makeplane/plane/blob/main/packages/types/src/workspace-views.ts) and gets persisted to the backend when users save custom views.

### What is the difference between static and custom workspace views in Plane?

Static views are predefined system views including "All Issues," "Assigned to Me," "Created by Me," and "Subscribed," identified by the `STATIC_VIEW_TYPES` constant. These store filter and display preferences locally in the browser via `handleIssuesLocalFilters.set`. Custom views, however, are user-created configurations that persist to the database through the `WorkspaceViewService` API, ensuring settings survive across sessions and devices.

### Where does Plane define available sorting options for workspace views?

Available sorting keys and directions are defined in [`packages/constants/src/views.ts`](https://github.com/makeplane/plane/blob/main/packages/constants/src/views.ts). The standard keys include `name`, `created_at`, and `updated_at`, with sort directions of `asc` or `desc`. These constants drive the dropdown options in the UI and validate the `order_by` and `sort` fields within the `display_filters` object of a workspace view.

### How can I programmatically update a workspace view's filters in Plane?

To programmatically update filters, instantiate `WorkspaceViewService` from `@plane/services` and call the `update` method with the workspace slug, view ID, and new filter configuration. Pass the updated `rich_filters` expression and `display_filters` object to modify both the query logic and presentation layer. The service sends a PATCH request to `/api/workspaces/:slug/views/:id/` to persist the changes.