# How Workspace and Project Views Operate in Plane: MobX State Architecture Explained

> Understand Plane's workspace and project views. Learn how MobX and reactive caches power the user interface using WorkspaceStore and ProjectViewStore.

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

---

**Plane resolves workspaces from URL slugs using `WorkspaceStore` and renders project views through `ProjectViewStore`, which maintains a reactive cache of view definitions, filters, and favorites in `apps/web/core/store`.**

Plane's open-source project management platform implements a sophisticated state management layer using MobX. The system centers on two primary stores that handle workspace resolution and project view rendering, ensuring reactive updates across the React component tree. This architecture lives in the `apps/web` directory and manages everything from sidebar navigation preferences to custom view filtering.

## The Core State Stores

Plane's UI state is driven by two top-level MobX stores that separate workspace-level concerns from project-specific view management.

### WorkspaceStore

The **`WorkspaceStore`** ([`apps/web/core/store/workspace/index.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/workspace/index.ts)) manages the list of workspaces a user belongs to and computes the current active workspace based on the URL. It stores sidebar navigation preferences and project-level navigation settings in observable maps.

Key responsibilities include:
- **`fetchWorkspaces`** – Retrieves the user's workspace list
- **`getWorkspaceBySlug`** and **`getWorkspaceById`** – Lookup methods for workspace resolution
- **`fetchSidebarNavigationPreferences`** and **`updateSidebarPreference`** – Manage pinned items and ordering
- **`fetchProjectNavigationPreferences`** and **`updateProjectNavigationPreferences`** – Handle project-specific navigation state

### ProjectViewStore

The **`ProjectViewStore`** ([`apps/web/core/store/project-view/store.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/project-view/store.ts)) manages custom project views including list, kanban, calendar, and spreadsheet layouts. It maintains a cache of view definitions and handles filter state management.

Key actions include:
- **`fetchViews`** – Populates the `projectViewsMap` cache
- **`getProjectViews`** and **`getFilteredProjectViews`** – Retrieve views with or without filter application
- **`createView`**, **`updateView`**, **`deleteView`** – CRUD operations
- **`addViewToFavorites`** and **`removeViewFromFavorites`** – Persistence of user favorites
- **`updateFilters`** and **`clearAllFilters`** – Filter state management

## Workspace Resolution from URL

Plane determines the active workspace through a reactive dependency chain connecting the router to the store.

### Router Integration

The URL contains the workspace slug in the pattern `/[workspaceSlug]/…`. The router exposes this as `router.workspaceSlug`, which `WorkspaceStore.currentWorkspace` observes (lines 48‑53 of [`apps/web/core/store/workspace/index.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/workspace/index.ts)). When the slug changes, the computed property automatically looks up the matching `IWorkspace` object from the `workspaces` map.

### Sidebar Navigation Persistence

Navigation preferences are stored per-workspace in `navigationPreferencesMap` of type `IWorkspaceSidebarNavigation`. When users pin or reorder sidebar items, the store persists changes via the `WorkspaceService` API:

```tsx
// Pinning a sidebar item programmatically
await workspaceStore.updateSidebarPreference(
  workspaceSlug,
  "issues",
  { is_pinned: true }
);

```

## Project View Lifecycle

Project views follow a strict loading and rendering pattern that ensures data consistency across route changes.

### Loading Views on Route Entry

When entering a project route (`/[workspaceSlug]/projects/[projectId]`), the `ProjectWrapper` component invokes `useProjectView` to obtain the store instance and triggers data fetching:

```tsx
// apps/web/core/layouts/auth-layout/project-wrapper.tsx
const { fetchViews } = useProjectView();
useEffect(() => {
  if (workspaceSlug && projectId) fetchViews(workspaceSlug, projectId);
}, [workspaceSlug, projectId]);

```

This populates the `projectViewsMap: Record<string, IProjectView[]>` cache, keyed by `projectId`.

### Rendering and Filtering

The `ViewsList` component reads from the store via the hook and renders `ProjectViewListItem` components:

```tsx
// apps/web/core/components/views/views-list.tsx
const { getProjectViews, getFilteredProjectViews, loader } = useProjectView();
const projectViews = getProjectViews(projectId?.toString());
const filteredProjectViews = getFilteredProjectViews(projectId?.toString());

```

`getFilteredProjectViews` applies the current filter state stored in the `filters` object (keyed by view ID) to the raw view array.

### CRUD and Filter Operations

The `CreateUpdateProjectViewModal` handles both creation and updates through the same form component. On submission, it delegates to the store:

```tsx
// apps/web/core/components/views/modal.tsx
const { createView, updateView } = useProjectView();
const handleFormSubmit = async (values) => {
  if (isEdit) await updateView(viewId, values);
  else await createView(values);
};

```

Filter updates propagate through `updateFilters` in [`view-list-header.tsx`](https://github.com/makeplane/plane/blob/main/view-list-header.tsx):

```tsx
// apps/web/core/components/views/view-list-header.tsx
const { filters, updateFilters } = useProjectView();
const onFilterChange = (newExpr) => updateFilters(viewId, newExpr);

```

## Integration Between Workspace and Project Views

The workspace slug scopes every API request in the project view layer. Store methods receive both `workspaceSlug` and `projectId` parameters to fetch appropriate data.

When users switch workspaces via the sidebar menu, the router updates `router.workspaceSlug`. This triggers `WorkspaceStore.currentWorkspace` to recompute, causing mounted `ProjectWrapper` components to automatically re-run their `useEffect` hooks and fetch the new project's view set.

Favorite view handling persists per-workspace through `addViewToFavorites` and `removeViewFromFavorites`, ensuring consistent favorites across projects within the same workspace.

## Practical Implementation Examples

### Accessing the Current Workspace

Use the `useWorkspace` hook to reactively access workspace data in components:

```tsx
import { observer } from "mobx-react-lite";
import { useWorkspace } from "@/hooks/store/use-workspace";

const WorkspaceTitle = observer(() => {
  const { currentWorkspace } = useWorkspace();
  if (!currentWorkspace) return null;
  return <h1>{currentWorkspace.name}</h1>;
});

```

*Key file:* [`apps/web/core/hooks/store/use-workspace.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/hooks/store/use-workspace.ts)

### Listing Project Views

Fetch and display views with automatic cache updates:

```tsx
import { observer } from "mobx-react-lite";
import { useProjectView } from "@/hooks/store/use-project-view";

const ProjectViews = observer(({ projectId }: { projectId: string }) => {
  const { getProjectViews, fetchViews } = useProjectView();

  useEffect(() => {
    fetchViews(projectId);
  }, [projectId]);

  const views = getProjectViews(projectId) ?? [];

  return (
    <ul>
      {views.map((v) => (
        <li key={v.id}>{v.name}</li>
      ))}
    </ul>
  );
});

```

*Key file:* [`apps/web/core/hooks/store/use-project-view.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/hooks/store/use-project-view.ts)

### Programmatic Filter Updates

Apply filters to a specific view using the store's filter API:

```tsx
import { useProjectView } from "@/hooks/store/use-project-view";

function applyMyFilter(viewId: string) {
  const { updateFilters } = useProjectView();
  const myFilter = {
    operator: "AND",
    operands: [{ field: "status", value: "TODO", condition: "is" }],
  };
  updateFilters(viewId, myFilter);
}

```

*Key file:* [`apps/web/core/store/project-view/store.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/project-view/store.ts)

## Summary

- **WorkspaceStore** ([`apps/web/core/store/workspace/index.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/workspace/index.ts)) resolves the current workspace from URL slugs and manages sidebar navigation preferences in `navigationPreferencesMap`.
- **ProjectViewStore** ([`apps/web/core/store/project-view/store.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/project-view/store.ts)) maintains a reactive cache of views in `projectViewsMap` and handles CRUD operations, favorites, and filtering.
- **Route Integration**: `ProjectWrapper` triggers `fetchViews` when project routes load, while `WorkspaceStore` observes `router.workspaceSlug` for automatic workspace switching.
- **Filter State**: Filters are stored per-view-ID in the `filters` object and applied via `getFilteredProjectViews`.
- **API Scoping**: All operations require `workspaceSlug` and `projectId` parameters to ensure proper data isolation.

## Frequently Asked Questions

### How does Plane determine the current active workspace from the URL?

Plane extracts the workspace slug from the URL pattern `/[workspaceSlug]/…` and exposes it through the router as `router.workspaceSlug`. The `WorkspaceStore.currentWorkspace` computed property (lines 48‑53 of [`apps/web/core/store/workspace/index.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/workspace/index.ts)) observes this value and performs a lookup in the `workspaces` map to return the matching `IWorkspace` object. When the URL changes, this reactive dependency automatically updates the active workspace throughout the application.

### What is the difference between WorkspaceStore and ProjectViewStore?

**WorkspaceStore** manages macro-level workspace data including the list of workspaces a user belongs to, the current workspace derived from the URL, and sidebar navigation preferences. **ProjectViewStore** handles micro-level project view definitions including list, kanban, calendar, and spreadsheet layouts, along with their associated filters and favorites. While WorkspaceStore persists navigation preferences per-workspace, ProjectViewStore caches view definitions in `projectViewsMap` keyed by `projectId`.

### How are project view filters stored and applied in Plane?

Filters are stored in the `ProjectViewStore.filters` object using the view ID as the key. When a user modifies filters through the UI (via [`view-list-header.tsx`](https://github.com/makeplane/plane/blob/main/view-list-header.tsx)), the component calls `updateFilters(viewId, newExpression)`. The `getFilteredProjectViews(projectId)` method then applies these stored filters to the raw view array from `projectViewsMap`, returning a computed filtered list that automatically updates UI components using MobX observables.

### How do favorite views work across different workspaces?

Favorite views are managed through `ProjectViewStore.addViewToFavorites` and `removeViewFromFavorites`. These methods persist favorite status per-workspace, meaning a user's favorite view list is consistent across all projects within the same workspace but isolated from other workspaces. The store ensures that favorites are properly scoped when fetching views via `fetchViews(workspaceSlug, projectId)`.