# Design Patterns Used in the Plane Codebase: A Technical Deep Dive

> Explore seven design patterns like Decorator, Singleton, and Factory in the Plane codebase. Learn how they build a modular, type-safe architecture for HTTP handling, state management, and UI.

- Repository: [Plane/plane](https://github.com/makeplane/plane)
- Tags: deep-dive
- Published: 2026-08-25

---

**The Plane codebase employs seven distinct design patterns—Decorator, Singleton, Observer, Factory, Repository, Command, and MVC-like separation—to create a modular, type-safe architecture that separates concerns across HTTP handling, state management, and UI rendering.**

The open-source project management platform Plane (makeplane/plane) demonstrates enterprise-grade TypeScript architecture through disciplined application of established design patterns. Analysis of the `preview` branch reveals how these patterns work together to maintain clean separation of concerns, ensure type safety across the monorepo, and enable scalable feature development without regression risks.

## Decorator Pattern for HTTP and WebSocket Abstraction

Plane uses the **Decorator Pattern** to wrap service methods with declarative HTTP and WebSocket handling logic. Located in [`packages/decorators/src/rest.ts`](https://github.com/makeplane/plane/blob/main/packages/decorators/src/rest.ts) and [`packages/decorators/src/websocket.ts`](https://github.com/makeplane/plane/blob/main/packages/decorators/src/websocket.ts), these decorators automatically attach request routing, authentication headers, and error handling to class methods.

This approach eliminates boilerplate network code from business logic. When you apply the `@rest` decorator to a service class, it intercepts method calls to inject base URLs, headers, and response parsing:

```typescript
import { rest } from '@plane/decorators';

@rest('/api/workspaces/:workspaceSlug/projects')
class ProjectService {
  async getProjects(workspaceSlug: string) {
    // The decorator automatically prefixes the URL and injects auth headers
    return this.get({ workspaceSlug });
  }
}

```

The implementation in [`packages/decorators/src/rest.ts`](https://github.com/makeplane/plane/blob/main/packages/decorators/src/rest.ts) handles the cross-cutting concerns of API communication, allowing developers to write endpoint logic that focuses purely on data transformation rather than transport mechanics.

## Singleton and Service Locator Patterns

The application core follows the **Singleton Pattern** combined with **Service Locator** behavior to ensure single instances manage API interactions. Services located in `apps/web/core/services/`—such as [`project.service.ts`](https://github.com/makeplane/plane/blob/main/project.service.ts)—are instantiated once and injected through Angular-style dependency injection systems.

This singleton approach prevents duplicate network clients and maintains consistent state across components. For global client-side state, Plane exports single store instances that act as singletons:

```typescript
import { makeAutoObservable } from 'mobx';

class ProjectStore {
  projects = [];

  constructor() {
    makeAutoObservable(this);
  }

  setProjects(list: Project[]) {
    this.projects = list;
  }
}

// Export a single instance used throughout the app
export const projectStore = new ProjectStore();

```

Reference: [`packages/shared-state/src/index.ts`](https://github.com/makeplane/plane/blob/main/packages/shared-state/src/index.ts)

## Observer Pattern with MobX

State management relies on the **Observer Pattern** via MobX observables in [`packages/shared-state/src/index.ts`](https://github.com/makeplane/plane/blob/main/packages/shared-state/src/index.ts). UI components subscribe to observable store properties, automatically re-rendering when underlying data changes without explicit event wiring.

This pub-sub architecture decouples state producers from consumers. When `projectStore.setProjects()` updates the array, MobX notifies all observing React components to trigger efficient, targeted re-renders rather than full page refreshes.

## Factory Pattern for Importer Creation

Plane implements the **Factory Pattern** in [`packages/types/src/importer/github-importer.ts`](https://github.com/makeplane/plane/blob/main/packages/types/src/importer/github-importer.ts) to instantiate concrete importer classes based on runtime configuration. This abstraction hides complex initialization logic from calling code, allowing the system to support multiple providers through a unified interface:

```typescript
import { GitHubImporter } from './github-importer';
import { GitLabImporter } from './gitlab-importer';

export function createImporter(provider: 'github' | 'gitlab') {
  switch (provider) {
    case 'github':
      return new GitHubImporter();
    case 'gitlab':
      return new GitLabImporter();
  }
}

```

The factory function encapsulates the decision logic for which concrete class to instantiate, making it trivial to add new importers (like Jira or Azure DevOps) without modifying existing consumer code.

## Repository Pattern for Data Access

Data access follows the **Repository Pattern**, observable in modules like [`packages/constants/src/auth/core.ts`](https://github.com/makeplane/plane/blob/main/packages/constants/src/auth/core.ts). These repository-style modules expose clean CRUD methods while hiding the underlying REST implementation details from the rest of the application.

By centralizing data persistence logic in repository classes, Plane ensures that changes to API endpoints or authentication mechanisms require updates only in isolated repository files, leaving business logic in services and components untouched.

## Command Pattern for Filter Operations

Rich-filter functionality utilizes the **Command Pattern** as implemented in [`packages/constants/src/rich-filters/option.ts`](https://github.com/makeplane/plane/blob/main/packages/constants/src/rich-filters/option.ts). Filter options are modeled as command objects that encapsulate both the action and parameters needed to transform query parameters.

This pattern separates UI interactions from data processing logic. When users select filter options, the system executes command objects to modify query states, enabling undo/redo functionality and complex filter composition without tangling UI code with query construction algorithms.

## MVC-Like Architectural Separation

While not strict MVC, Plane organizes code into a **Model-View-Controller-like** separation:
- **Models**: TypeScript interfaces in `packages/types/*` define data structures
- **Views**: Next.js pages in `apps/web/pages/*` handle presentation
- **Controllers**: Service classes in `apps/web/core/services/*` orchestrate data flow

This separation creates a unidirectional data flow where Views dispatch actions to Services, Services update Models via Repositories, and MobX Observables notify Views of changes.

## Summary

- **Decorators** in `packages/decorators/src/` handle cross-cutting HTTP concerns automatically
- **Singleton Services** ensure single API client instances exist throughout the application lifecycle
- **MobX Observers** enable reactive UI updates without manual event subscription management
- **Factory methods** abstract importer instantiation to support multiple third-party integrations
- **Repositories** encapsulate data access logic behind consistent CRUD interfaces
- **Commands** model filter operations as discrete, executable objects
- **MVC-like layers** enforce clear boundaries between data, logic, and presentation

## Frequently Asked Questions

### Does Plane use dependency injection for its services?

Yes. According to the makeplane/plane source code, services in `apps/web/core/services/` follow Angular-style dependency injection patterns where classes are instantiated once as singletons and injected into components. This ensures a single source of truth for API interactions across the UI while maintaining testability through mock injection.

### How does Plane manage global state without prop drilling?

The Plane codebase uses the **Observer Pattern** via MobX stores defined in [`packages/shared-state/src/index.ts`](https://github.com/makeplane/plane/blob/main/packages/shared-state/src/index.ts). These singleton stores export single instances that any component can import directly. When store properties marked with `makeAutoObservable` change, MobX automatically triggers re-renders only in components observing those specific properties, eliminating the need for prop drilling or context providers.

### What pattern handles API endpoint definition in Plane?

The **Decorator Pattern** handles API abstraction. Located in [`packages/decorators/src/rest.ts`](https://github.com/makeplane/plane/blob/main/packages/decorators/src/rest.ts), the `@rest` decorator wraps service classes to automatically inject base URLs, authentication headers, and error handling. This keeps endpoint definitions declarative and removes repetitive HTTP configuration code from business logic methods.

### Can new importers be added without modifying existing code?

Yes. The **Factory Pattern** implementation in [`packages/types/src/importer/github-importer.ts`](https://github.com/makeplane/plane/blob/main/packages/types/src/importer/github-importer.ts) allows adding new importers by extending the factory's switch statement or mapping. Since consumers call `createImporter()` with a provider string rather than instantiating classes directly, adding support for new platforms (like Bitbucket or Linear) requires changes only within the factory module, preserving the open/closed principle.