How Plane's View System for Saved Filters and Custom Queries Works

Plane implements a three-layer architecture that persists filter configurations as JSON objects in a Django backend, synchronizes them across teammates via a MobX shared-state store, and renders them through dedicated UI components that apply stored filters to issue lists.

Plane's View system transforms temporary search queries into reusable, shareable workflows within a project. This feature allows teams to save complex filter combinations and access them instantly across all devices and sessions. The implementation in the makeplane/plane repository demonstrates a clean separation between data persistence, state management, and presentation layers.

Architecture Overview

Plane organizes its View system into three distinct layers that handle data storage, state synchronization, and user interaction. Each layer has specific responsibilities and communicates through well-defined interfaces.

Backend Model Layer

The foundation rests on a Django model that stores view metadata and filter definitions. The backend exposes REST endpoints at /api/projects/:project_id/views/ supporting full CRUD operations (GET, POST, PATCH, DELETE). Each view record contains a name, description, privacy flag (is_private), sort order, and a filter_json field that stores the filter criteria as a serialized JSON object.

Shared-State Store Layer

The middle layer manages reactive state using MobX. The ViewStore class in packages/shared-state/src/view-store.ts maintains observables for views (the list of available views) and selectedView (the currently active filter set). The store provides methods including loadViews(), createView(), updateView(), and deleteView() that wrap API calls and update local state. WebSocket listeners in packages/shared-state/src/websocket.ts push real-time updates to all connected clients when teammates modify views.

UI Component Layer

The presentation layer renders view management interfaces and applies filters to issue lists. Key components reside in packages/ui/src/views/, including ViewDropdown for selection, ViewDialog for creation and editing, and ViewItem for individual list entries. The IssueList component (located in packages/ui/src/issue-list/issue-list.tsx) reads the selected view from the store and translates the stored JSON filter into query parameters for the issue API.

Creating and Persisting Views

When users save a filter configuration, the system serializes the current filter state and persists it through the API client defined in packages/api/src/client/views.ts.

The creation flow begins when ViewCreateDialog collects metadata such as the view name and privacy setting. On submission, the component invokes viewStore.createView() with a payload structure containing name, description, is_private, and filter_json. The store posts this data to POST /api/projects/:id/views/ and adds the returned view object to the reactive views array.

import { useViewStore } from 'packages/shared-state';
import { useCurrentFilters } from 'packages/ui';

function SaveCurrentFilterButton() {
  const viewStore = useViewStore();
  const { filter } = useCurrentFilters();

  const onSave = async () => {
    await viewStore.createView({
      name: 'My important tickets',
      description: '',
      is_private: false,
      filter_json: JSON.stringify(filter),
    });
  };

  return <button onClick={onSave}>Save as View</button>;
}

Loading and Selecting Views

When a project page mounts, the system initializes the view list through viewStore.loadViews(projectId). This method fetches all project views via GET /api/projects/:id/views/ and populates the MobX observable.

The ViewDropdown component renders the available options and handles selection changes. When a user selects a view, the store updates selectedView and triggers the issue list to re-fetch data using the stored filter criteria.

import { ViewDropdown } from 'packages/ui/src/views/view-dropdown';
import { useViewStore } from 'packages/shared-state';

export function IssueToolbar() {
  const viewStore = useViewStore();

  return (
    <div className="flex items-center gap-2">
      <ViewDropdown
        views={viewStore.views}
        selectedId={viewStore.selectedView?.id}
        onSelect={viewStore.selectView}
      />
    </div>
  );
}

Applying Views to Issue Lists

The issue list integration demonstrates how the system bridges stored queries with live data. When selectedView changes, the list component parses the filter_json string and passes the resulting object to the issue service.

import { useEffect } from 'react';
import { useViewStore } from 'packages/shared-state';
import { useIssueService } from 'packages/api';

function IssueList() {
  const viewStore = useViewStore();
  const issueService = useIssueService();

  useEffect(() => {
    const filter = viewStore.selectedView?.filter_json
      ? JSON.parse(viewStore.selectedView.filter_json)
      : {};
    issueService.fetchIssues(filter);
  }, [viewStore.selectedView]);

  return null; // render list implementation
}

Key Implementation Files

The View system spans multiple packages in the Plane monorepo. The following files contain the core implementation details:

Summary

Plane's View system architecture provides a robust solution for persisting and sharing filter configurations across project teams. Key implementation details include:

  • Backend persistence via Django models storing filter criteria as JSON with privacy controls
  • Reactive state management through the MobX ViewStore in packages/shared-state/src/view-store.ts
  • Real-time synchronization using WebSocket connections to keep view lists consistent across clients
  • Modular UI components in packages/ui/src/views/ handling creation, selection, and deletion workflows
  • Seamless integration with issue lists that parse stored JSON filters into live API queries

Frequently Asked Questions

How does Plane synchronize views across multiple users?

Plane utilizes WebSocket connections managed in packages/shared-state/src/websocket.ts to broadcast view changes to all connected clients. When one user creates, updates, or deletes a view, the server pushes an event that triggers the ViewStore to refresh its local state, ensuring every teammate sees the current view list without manual page refreshes.

What data format does Plane use to store filter criteria?

The system stores filter criteria as serialized JSON strings in the filter_json field of the View model. This approach allows flexible storage of complex filter objects containing multiple conditions, operators, and values without requiring rigid database schema migrations when filter capabilities expand.

Can views be private to a single user or must they be shared?

Views support both privacy modes through the is_private boolean flag. When is_private is set to true, the backend restricts visibility to the creating user only. Public views (is_private: false) appear in the view list for all project members, enabling teams to share standard filter configurations for common workflows.

Where is the View state managed in the Plane frontend?

All view-related state lives in the ViewStore class located at packages/shared-state/src/view-store.ts. This MobX store maintains observables for the view collection and selected view, providing methods like loadViews(), createView(), and selectView() that components across the application import to read or modify view data.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →