# How Plane's Notification System Handles Real-Time Alerts

> Discover how Plane's notification system handles real-time alerts using MobX and REST APIs for instant UI updates without WebSockets. Learn more now.

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

---

**Plane delivers real-time notification alerts through a reactive MobX store combined with REST API endpoints, eliminating the need for persistent WebSocket connections while maintaining instant UI updates.**

Plane's notification system balances server-side state management with client-side reactivity to create a responsive user experience. The system centers on the `WorkspaceNotificationService` for API communication and the `WorkspaceNotificationStore` for state management, working together to keep the notification panel synchronized. According to the makeplane/plane source code, this approach ensures users see immediate visual feedback when interacting with alerts, even though the underlying transport uses standard HTTP requests rather than persistent socket connections.

## The WorkspaceNotificationService API Layer

The notification service acts as the central HTTP client for all alert-related operations. Defined in [[`packages/services/src/workspace/notification.service.ts`](https://github.com/makeplane/plane/blob/main/packages/services/src/workspace/notification.service.ts)](https://github.com/makeplane/plane/blob/preview/packages/services/src/workspace/notification.service.ts), this service exposes methods that map to specific REST endpoints for fetching, updating, and managing notification states.

### Available Endpoints and Methods

The service implements a complete CRUD interface for notification management:

- **`getUnreadCount`** – Calls `GET /api/workspaces/:workspaceSlug/users/notifications/unread/` to retrieve total and mention-specific unread counts
- **`list`** – Fetches paginated notifications via `GET /api/workspaces/:workspaceSlug/users/notifications` with support for filtering and snoozed/archived flags
- **`update`** – Sends `PATCH` requests to modify notification properties like snooze status
- **`markAsRead`** – Posts to `…/notifications/:id/read/` to mark individual alerts as read
- **`markAsUnread`** – Deletes the read status via `DELETE …/notifications/:id/read/`
- **`archive`** and **`unarchive`** – Manage archived state through `POST` and `DELETE` requests to the archive endpoint
- **`markAllAsRead`** – Bulk updates all filtered notifications via `POST …/notifications/mark-all-read/`

## The MobX Store: Reactive State Management

The `WorkspaceNotificationStore` located at [[`apps/web/core/store/notifications/workspace-notifications.store.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/notifications/workspace-notifications.store.ts)](https://github.com/makeplane/plane/blob/preview/apps/web/core/store/notifications/workspace-notifications.store.ts) implements the client-side state container using MobX observables. This store maintains the single source of truth for notification data and automatically triggers React re-renders when state changes.

### Fetching and Storing Notifications

When the application needs to display alerts, the store's `getNotifications` method invokes the service's `list` endpoint. Upon receiving a `TNotificationPaginatedInfo` payload, the store extracts the `results` array and processes each item through `mutateNotifications`. This creates or updates `Notification` model instances defined in [[`apps/web/core/store/notifications/notification.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/notifications/notification.ts)](https://github.com/makeplane/plane/blob/preview/apps/web/core/store/notifications/notification.ts).

```typescript
// Instantiate the service
const notificationService = new WorkspaceNotificationService();

// Fetch the first page of notifications
await workspaceNotificationStore.getNotifications('my-workspace-slug');

```

### Handling Unread Counts

The store maintains unread notification counters through the `getUnreadNotificationsCount` method. This calls the service's `getUnreadCount` endpoint and stores the result in the observable `unreadNotificationsCount` property, enabling real-time badge updates in the UI.

```typescript
// Get unread counts for the current workspace
const unread = await notificationService.getUnreadCount('my-workspace-slug');

```

### Local State Mutations

When users interact with notifications, the store applies optimistic updates. For example, marking a notification as read immediately calls `notification.mutateNotification({ read_at: new Date().toUTCString() })` on the local model instance before or after the API call completes. This ensures the UI reflects changes instantly without waiting for server confirmation or full page refreshes.

```typescript
// Mark a single notification as read with immediate UI feedback
await notificationService
  .markAsRead('my-workspace-slug', 'notification-id-123')
  .then((updated) => {
    console.log('Marked read:', updated);
  });

```

## Real-Time UI Updates Without WebSockets

Unlike traditional real-time systems that rely on WebSocket connections, Plane achieves reactive updates through a combination of fast API calls and MobX's observable state system. When backend processes—such as the task defined in [[`apps/api/plane/bgtasks/notification_task.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/bgtasks/notification_task.py)](https://github.com/makeplane/plane/blob/preview/apps/api/plane/bgtasks/notification_task.py)—create new notification records, the client fetches fresh data during the next panel interaction or manual refresh.

The reactive chain works as follows:

1. **User Action** triggers a service method (e.g., `markAsRead`)
2. **API Response** confirms the server state change
3. **Store Mutation** updates the MobX observable (e.g., `notification.mutateNotification`)
4. **Computed Properties** like `notificationIdsByWorkspaceId` and `notificationLiteByNotificationId` recalculate automatically
5. **React Re-render** occurs because components wrapped in `observer()` from `mobx-react-lite` detect observable changes

This architecture provides a "real-time-like" experience where the UI appears instantly synchronized with server state.

## Implementation Examples

### Consuming Notifications in React Components

Components access the store through the `useRootStore` hook (or the specific hook from [[`apps/web/core/hooks/store/notifications/use-workspace-notifications.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/hooks/store/notifications/use-workspace-notifications.ts)](https://github.com/makeplane/plane/blob/preview/apps/web/core/hooks/store/notifications/use-workspace-notifications.ts)) and automatically re-render when notification data changes. The [[`notification-app-sidebar-option.tsx`](https://github.com/makeplane/plane/blob/main/notification-app-sidebar-option.tsx)](https://github.com/makeplane/plane/blob/preview/apps/web/core/components/workspace-notifications/notification-app-sidebar-option.tsx) component demonstrates this pattern by rendering individual notification entries that respond to store updates.

```tsx
import { observer } from 'mobx-react-lite';
import { useRootStore } from '@/hooks/store-hooks';

const NotificationList = observer(() => {
  const { notification } = useRootStore();
  const ids = notification.notificationIdsByWorkspaceId('workspace-id') ?? [];

  return (
    <ul>
      {ids.map((id) => (
        <li key={id}>{notification.notifications[id].title}</li>
      ))}
    </ul>
  );
});

```

### Bulk Operations

The store supports batch operations that minimize API calls while maintaining state consistency:

```typescript
// Bulk-mark all filtered notifications as read
await workspaceNotificationStore.markAllNotificationsAsRead('my-workspace-slug');

```

### Individual Notification Model

Each notification wraps its data in a model class that provides type-safe mutation methods:

```typescript
// Inside workspace-notifications.store.ts
notification.mutateNotification({ 
  read_at: new Date().toUTCString(),
  archived_at: null 
});

```

## Summary

- **Plane's notification system** combines REST APIs with MobX reactivity to deliver real-time UI updates without WebSocket dependencies.
- **WorkspaceNotificationService** handles all HTTP communication to endpoints for fetching, updating, and bulk-managing notifications.
- **WorkspaceNotificationStore** maintains observable state using MobX, with computed properties like `notificationIdsByWorkspaceId` providing filtered data to components.
- **Optimistic updates** occur through the `Notification` model's `mutateNotification` method, ensuring immediate visual feedback when users mark items read, unread, or archived.
- **React components** consume the store via `observer()` HOCs from `mobx-react-lite`, enabling automatic re-renders when notification state changes.

## Frequently Asked Questions

### Does Plane use WebSockets for real-time notification alerts?

No, Plane does not currently use persistent WebSocket connections for notifications. Instead, the system relies on fast REST API calls combined with MobX's reactive state management to provide immediate UI updates. When the backend creates new notifications via background tasks like [`notification_task.py`](https://github.com/makeplane/plane/blob/main/notification_task.py), the client fetches fresh data on the next user interaction or manual refresh.

### How does Plane keep the unread notification count synchronized?

The `WorkspaceNotificationStore` maintains an `unreadNotificationsCount` observable that updates whenever `getUnreadNotificationsCount` is called. This method queries the `GET /api/workspaces/:workspaceSlug/users/notifications/unread/` endpoint, ensuring the UI badge reflects the current server state. The store also updates this count optimistically when users mark individual notifications as read or unread.

### What happens when a user marks a notification as read?

When `markAsRead` is invoked, the service sends a `POST` request to the read endpoint. Upon success, the store immediately calls `mutateNotification` on the local `Notification` instance to update the `read_at` timestamp. Because the store uses MobX observables, React components consuming this data re-render instantly, providing real-time visual feedback without waiting for a full data refresh.

### Where is the notification backend logic implemented?

The backend logic for creating notification records resides in [[`apps/api/plane/bgtasks/notification_task.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/bgtasks/notification_task.py)](https://github.com/makeplane/plane/blob/preview/apps/api/plane/bgtasks/notification_task.py). This background task generates notifications when triggering events occur (such as issue comments or mentions), while the frontend service layer in [`packages/services/src/workspace/notification.service.ts`](https://github.com/makeplane/plane/blob/main/packages/services/src/workspace/notification.service.ts) handles all client-side API interactions.