MobX Store Architecture for Global State Management in Plane

Plane implements a single-root MobX store pattern where a centralized CoreRootStore aggregates dozens of feature-specific stores and exposes them to React components via a dedicated StoreContext.

The frontend of Plane (makeplane/plane) relies on a hierarchical MobX store architecture to handle complex global state management across its project management interface. This architecture centers on a single root store that instantiates and wires together domain-specific stores for workspaces, issues, projects, and UI themes. By combining MobX's reactive observables with React's context API, Plane delivers fine-grained state updates while keeping component code declarative and type-safe.

Core Components of the Store Architecture

Plane’s MobX store architecture consists of three primary layers: the root store that aggregates all state, the React context that distributes it, and the feature-specific stores that encapsulate domain logic.

CoreRootStore and RootStore Hierarchy

The CoreRootStore class serves as the backbone of Plane’s state management system. Located in apps/web/core/store/root.store.ts, this class is responsible for instantiating every sub-store—including projectRoot, issue, router, theme, and user management stores—and wiring them together at application startup.

When an application requires additional stores beyond the core set, Plane uses an extension pattern. The Community Edition (CE) app extends CoreRootStore in apps/web/ce/store/root.store.ts, adding only the stores unique to that deployment while inheriting the full core functionality. This hierarchy ensures consistent state management across different Plane deployments while allowing for customization.

StoreContext and StoreProvider

State distribution relies on StoreContext, a React context created via React.createContext<RootStore | undefined>(undefined) and defined in apps/web/lib/store-context.ts. The StoreProvider component—typically placed in apps/web/pages/_app.tsx—instantiates the root store once and supplies it to the component tree:

import { StoreContext } from '@/lib/store-context';
import { RootStore } from '@/store/root.store';

function MyApp({ Component, pageProps }) {
  const rootStore = useRef(new RootStore()).current;

  return (
    <StoreContext.Provider value={rootStore}>
      <Component {...pageProps} />
    </StoreContext.Provider>
  );
}

Feature-Specific Sub-Stores

Each domain area manages its own state through dedicated stores instantiated by the root store. For example, WorkspaceStore in packages/shared-state/src/store/workspace.store.ts handles workspace data, while ProjectRootStore and IssueRootStore manage project and issue state respectively. These stores receive a reference to the root store during construction, enabling cross-store communication when needed.

Store Initialization Flow

The creation and wiring of Plane’s MobX store architecture follows a strict initialization sequence:

  1. Application startup creates a new RootStore() instance (or CoreRootStore() for admin/space apps).
  2. The RootStore constructor calls super() to invoke CoreRootStore’s constructor, which instantiates the router (new RouterStore()), command palette, instance, user, theme, and every domain store (new ProjectRootStore(this), new IssueRootStore(this), etc.).
  3. The constructor passes the root store (this) to sub-stores that require cross-store access, such as UserStore(this as unknown as RootStore).
  4. The instantiated root store is placed in React context via <StoreContext.Provider value={rootStore}>.
  5. React components consume the context through specialized hooks like useWorkspace() or useProject(), each calling useContext(StoreContext) and returning the specific sub-store required.

Accessing Stores in React Components

Components access global state through thin wrapper hooks rather than interacting with context directly. The useWorkspace hook in apps/web/core/hooks/store/use-workspace.ts demonstrates this pattern:

import { useWorkspace } from '@/hooks/store/use-workspace';

function WorkspaceHeader() {
  const workspace = useWorkspace();   // returns IWorkspaceRootStore
  return <h1>{workspace.currentWorkspace?.name ?? 'Loading…'}</h1>;
}

This approach provides type-safe access to specific store slices while keeping components decoupled from the store architecture's internal structure.

State Management Patterns

Plane leverages MobX’s makeObservable utility to define reactive properties. In packages/shared-state/src/store/workspace.store.ts, observable references are configured explicitly:

export class WorkspaceStore implements IWorkspaceStore {
  constructor(data: IWorkspaceStore) {
    makeObservable(this, {
      id:   observable.ref,
      name: observable.ref,
      createdAt: observable.ref,
      updatedAt: observable.ref,
    });
    this.id = data.id;
    this.name = data.name;
    // ...
  }
}

State mutations occur through actions that batch notifications. The fetchProjectStates method in apps/web/core/store/state.store.ts illustrates the pattern for async data fetching:

@action
async fetchProjectStates(workspaceSlug: string, projectId: string) {
  const response = await this.stateService.getStates(workspaceSlug, projectId);
  runInAction(() => {
    response.forEach(state => set(this.stateMap, [state.id], state));
    this.fetchedMap[projectId] = true;
  });
}

Because the method is decorated with @action, MobX batches change notifications, ensuring that components observing stateMap or fetchedMap re-render efficiently and predictably.

Summary

  • Plane uses a single-root MobX store (CoreRootStore) that aggregates all feature-specific stores into one hierarchical object.
  • The root store is instantiated once at startup and provided to React via StoreContext in apps/web/lib/store-context.ts.
  • Domain stores (workspace, issue, project) receive the root store reference during construction, enabling cross-store communication.
  • Components access state through thin wrapper hooks (e.g., useWorkspace, useProject) that read from StoreContext.
  • State updates use MobX @action decorators and makeObservable to ensure fine-grained reactivity and predictable update batches.

Frequently Asked Questions

What is the root store in Plane's MobX architecture?

The root store is the CoreRootStore class defined in apps/web/core/store/root.store.ts. It acts as a container that instantiates and holds references to all feature-specific stores such as WorkspaceRootStore, IssueRootStore, and RouterStore. This single root object is passed through React context to make global state accessible throughout the component tree.

How do React components access MobX stores in Plane?

Components access stores through specialized hooks rather than direct context consumption. Hooks like useWorkspace() (found in apps/web/core/hooks/store/use-workspace.ts) call useContext(StoreContext) internally and return the specific sub-store requested. This pattern provides type-safe access while keeping components decoupled from the store hierarchy's implementation details.

What pattern does Plane use for store initialization?

Plane follows a hierarchical constructor pattern where RootStore extends CoreRootStore. During instantiation, the constructor creates all sub-stores and passes this (the root store instance) to any sub-store requiring cross-store references. This occurs once at application startup in the StoreProvider component, typically located in apps/web/pages/_app.tsx.

How does Plane handle state updates with MobX?

State updates occur through class methods decorated with MobX's @action decorator, which ensures that all state changes within the method are batched into a single notification. For asynchronous operations, Plane uses runInAction to wrap state mutations after async calls complete. Observables are defined using makeObservable with observable.ref for efficient reference tracking, ensuring components only re-render when specific properties they observe actually change.

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 →