How Workspace Views Support Custom Filters, Sorting, and Saved Filter Persistence in Plane
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. 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 (listorkanban), 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 manages the state updates.
The store exposes the updateFilterExpression method to modify the filter tree:
// 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, including:
namecreated_atupdated_at
Sort direction accepts asc or desc values.
When users modify display options, the WorkspaceIssuesFilter processes updates through the updateFilters method:
// 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), 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. 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:
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 implements this logic:
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:
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
Updating Sort Order for Existing Views
To modify sorting preferences for an existing view:
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
Accessing Filter State in the Store
The MobX store maintains observable filter state accessible after fetching:
// 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
Summary
- Workspace views in Plane persist through three structured properties:
rich_filtersfor query logic,display_filtersfor sorting and layout, anddisplay_propertiesfor UI configuration. - Rich filters support complex nested expressions via
TWorkItemFilterExpression, managed by theWorkspaceIssuesFilterstore inapps/web/core/store/issue/workspace/filter.store.ts. - Display filters control layout modes, grouping, and sorting keys defined in
packages/constants/src/views.ts, with state updates handled through MobX actions. - Persistence occurs via
WorkspaceViewServiceinpackages/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 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. 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.
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 →