# How Plane Handles API Data Fetching: Axios, Services, and MobX Stores

> Discover how Plane fetches API data using Axios, domain-specific services, and MobX stores for efficient data flow and UI updates. Learn about their centralized APIService.

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

---

**Plane abstracts all HTTP communication through a centralized `APIService` class built on Axios, which domain-specific services extend to consume REST endpoints, while MobX stores orchestrate the data flow between these services and the UI components.**

The open-source project management platform Plane (makeplane/plane) implements a layered architecture for API data fetching that cleanly separates HTTP configuration from business logic. This pattern centralizes request handling, authentication redirects, and error formatting in a single base class, while allowing domain-specific services to declare only the endpoints they consume.

## The APIService Base Class

All network requests in Plane originate from the abstract `APIService` class defined in [`apps/web/core/services/api.service.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/services/api.service.ts). This class wraps Axios with application-wide defaults and interceptors.

### Axios Configuration and Interceptors

The constructor initializes an Axios instance with credentials enabled and attaches a response interceptor that handles authentication failures globally:

```typescript
// apps/web/core/services/api.service.ts
export abstract class APIService {
  protected baseURL: string;
  private axiosInstance: AxiosInstance;

  constructor(baseURL: string) {
    this.baseURL = baseURL;
    this.axiosInstance = create({ baseURL, withCredentials: true });
    this.setupInterceptors();
  }

  private setupInterceptors() {
    this.axiosInstance.interceptors.response.use(
      response => response,
      error => {
        if (error.response?.status === 401) {
          const currentPath = window.location.pathname;
          window.location.replace(`/${currentPath ? `?next_path=${currentPath}` : ``}`);
        }
        return Promise.reject(error);
      }
    );
  }
}

```

This configuration ensures every API call automatically includes cookies for cross-origin requests and triggers a redirect to the login page when the server returns a 401 status, preserving the attempted path in the query string for post-login redirection.

### HTTP Method Wrappers

The class exposes thin wrappers around Axios methods that all derived services inherit:

```typescript
// apps/web/core/services/api.service.ts
get(url: string, params = {}, config = {}) { … }
post(url: string, data = {}, config = {}) { … }
put(url: string, data = {}, config = {}) { … }
patch(url: string, data = {}, config = {}) { … }
delete(url: string, data?, config = {}) { … }
request(config = {}) { … }

```

By inheriting these methods, domain services maintain consistent error handling and response formatting without duplicating Axios configuration logic.

## Domain-Specific Services

Concrete services extend `APIService` to define endpoints for specific resources. The `WorkspaceService` in [`apps/web/core/services/workspace.service.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/services/workspace.service.ts) demonstrates this pattern for workspace-related API data fetching:

```typescript
// apps/web/core/services/workspace.service.ts
export class WorkspaceService extends APIService {
  constructor() {
    super(API_BASE_URL);
  }

  async userWorkspaces(): Promise<IWorkspace[]> {
    return this.get("/api/users/me/workspaces/")
      .then(r => r?.data)
      .catch(e => { throw e?.response?.data; });
  }

  async getWorkspace(workspaceSlug: string): Promise<IWorkspace> {
    return this.get(`/api/workspaces/${workspaceSlug}/`)
      .then(r => r?.data)
      .catch(e => { throw e?.response; });
  }
}

```

Each method normalizes the response by extracting `r?.data` and standardizes errors by throwing `e?.response?.data`, ensuring upstream consumers receive predictable payloads whether the request succeeds or fails.

## Store Layer Integration

Plane uses MobX stores to manage reactive state and coordinate API data fetching. The `BaseWorkspaceRootStore` in [`apps/web/core/store/workspace/index.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/workspace/index.ts) consumes the `WorkspaceService` to populate observable state:

```typescript
// apps/web/core/store/workspace/index.ts
fetchWorkspaces = async () => {
  this.loader = true;
  try {
    const workspaceResponse = await this.workspaceService.userWorkspaces();
    runInAction(() => {
      workspaceResponse.forEach(ws => set(this.workspaces, [ws.id], ws));
    });
    return workspaceResponse;
  } finally {
    this.loader = false;
  }
};

```

This flow demonstrates the complete data fetching cycle: the store sets a loading state, invokes the service method, updates the observable map with the response data inside `runInAction`, and resets the loader regardless of success or failure.

## Complete Data Flow Example

To fetch and display workspaces in a React component, the architecture chains these layers together:

```typescript
import { useRootStore } from "@/store/root.store";

function WorkspaceList() {
  const { workspaceRoot } = useRootStore();
  
  useEffect(() => {
    workspaceRoot.fetchWorkspaces();
  }, []);

  if (workspaceRoot.loader) return <Spinner />;
  
  return (
    <ul>
      {Array.from(workspaceRoot.workspaces.values()).map(ws => (
        <li key={ws.id}>{ws.name}</li>
      ))}
    </ul>
  );
}

```

Alternatively, you can call the service layer directly for one-off operations:

```typescript
import { WorkspaceService } from "@/services/workspace.service";

const wsService = new WorkspaceService();
await wsService.createWorkspace({ name: "New Project", slug: "new-project" });

```

## Key Configuration Constants

All services consume the base URL from a centralized constants package:

- **API_BASE_URL**: Defined in [`packages/constants/src/swr.ts`](https://github.com/makeplane/plane/blob/main/packages/constants/src/swr.ts) and imported by every service constructor
- **withCredentials**: Set to `true` in `APIService` to ensure cookies are sent with cross-origin requests
- **Error Shape**: Consistent server error payloads accessed via `error.response.data`

## Summary

- **Centralized HTTP Layer**: The `APIService` class in [`apps/web/core/services/api.service.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/services/api.service.ts) configures Axios once with authentication interceptors and credentials.
- **Domain Isolation**: Services like `WorkspaceService` extend the base class to declare specific endpoints without handling HTTP boilerplate.
- **Reactive State Management**: MobX stores such as `BaseWorkspaceRootStore` orchestrate API data fetching and maintain observable state for UI components.
- **Automatic Auth Handling**: A 401 response triggers an immediate redirect to the login page with the current path preserved for redirection after authentication.
- **Consistent Error Format**: All service methods normalize errors to `error.response.data` for uniform error handling across the application.

## Frequently Asked Questions

### How does Plane handle authentication errors during API data fetching?

When any API request returns a 401 status code, the response interceptor defined in `APIService.setupInterceptors()` captures the error and redirects the browser to the login page. The interceptor preserves the current pathname in a `next_path` query parameter, allowing the application to redirect users back to their intended destination after successful authentication.

### What is the relationship between services and stores in Plane's architecture?

Services are responsible for the actual HTTP transport layer—constructing URLs, sending requests, and parsing responses—while stores manage application state and business logic. Stores instantiate services as private members and call their methods to trigger API data fetching, then update MobX observables with the results. This separation keeps network code isolated from UI components and state management logic.

### How do you add a new API endpoint in Plane?

Create a method in the appropriate domain service that extends `APIService`. Use the inherited `get()`, `post()`, `put()`, `patch()`, or `delete()` methods, passing the relative URL and any payload data. The method should return `this.get("/api/endpoint/").then(r => r?.data)` to maintain consistency with existing error handling and response parsing patterns.

### Why does Plane use an abstract base class for API requests rather than using Axios directly?

The `APIService` abstraction ensures every HTTP request shares the same base URL (from `API_BASE_URL`), credential configuration, and error handling logic. Without this base class, each service would need to duplicate interceptors for authentication redirects and error normalization, increasing maintenance overhead and risking inconsistent behavior across different domains of the application.