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

> Explore Plane's View system architecture. Learn how saved filters and custom queries are managed using a three-layer approach with Django, MobX, and UI components for efficient issue tracking.

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

---

**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`](https://github.com/makeplane/plane/blob/main/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`](https://github.com/makeplane/plane/blob/main/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`](https://github.com/makeplane/plane/blob/main/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`](https://github.com/makeplane/plane/blob/main/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.

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

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

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

- **[`packages/shared-state/src/view-store.ts`](https://github.com/makeplane/plane/blob/main/packages/shared-state/src/view-store.ts)** – MobX store managing view lists and selection state
- **[`packages/shared-state/src/websocket.ts`](https://github.com/makeplane/plane/blob/main/packages/shared-state/src/websocket.ts)** – WebSocket listener for real-time synchronization
- **[`packages/ui/src/views/view-dropdown.tsx`](https://github.com/makeplane/plane/blob/main/packages/ui/src/views/view-dropdown.tsx)** – Dropdown component for view selection
- **[`packages/ui/src/views/view-dialog.tsx`](https://github.com/makeplane/plane/blob/main/packages/ui/src/views/view-dialog.tsx)** – Dialog for creating and editing views
- **[`packages/ui/src/issue-list/issue-list.tsx`](https://github.com/makeplane/plane/blob/main/packages/ui/src/issue-list/issue-list.tsx)** – Integration point applying views to issue data
- **[`packages/api/src/client/views.ts`](https://github.com/makeplane/plane/blob/main/packages/api/src/client/views.ts)** – API client methods for view endpoints
- **[`packages/i18n/src/locales/en/project.json`](https://github.com/makeplane/plane/blob/main/packages/i18n/src/locales/en/project.json)** – Internationalization strings for view-related UI text
- **[`packages/i18n/src/locales/en/empty-state.json`](https://github.com/makeplane/plane/blob/main/packages/i18n/src/locales/en/empty-state.json)** – Empty state copy for views without results

## 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`](https://github.com/makeplane/plane/blob/main/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`](https://github.com/makeplane/plane/blob/main/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`](https://github.com/makeplane/plane/blob/main/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.