How Plane's Notification System Handles Real-Time Alerts
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/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– CallsGET /api/workspaces/:workspaceSlug/users/notifications/unread/to retrieve total and mention-specific unread countslist– Fetches paginated notifications viaGET /api/workspaces/:workspaceSlug/users/notificationswith support for filtering and snoozed/archived flagsupdate– SendsPATCHrequests to modify notification properties like snooze statusmarkAsRead– Posts to…/notifications/:id/read/to mark individual alerts as readmarkAsUnread– Deletes the read status viaDELETE …/notifications/:id/read/archiveandunarchive– Manage archived state throughPOSTandDELETErequests to the archive endpointmarkAllAsRead– Bulk updates all filtered notifications viaPOST …/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/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/preview/apps/web/core/store/notifications/notification.ts).
// 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.
// 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.
// 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/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:
- User Action triggers a service method (e.g.,
markAsRead) - API Response confirms the server state change
- Store Mutation updates the MobX observable (e.g.,
notification.mutateNotification) - Computed Properties like
notificationIdsByWorkspaceIdandnotificationLiteByNotificationIdrecalculate automatically - React Re-render occurs because components wrapped in
observer()frommobx-react-litedetect 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/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/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.
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:
// 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:
// 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
notificationIdsByWorkspaceIdproviding filtered data to components. - Optimistic updates occur through the
Notificationmodel'smutateNotificationmethod, ensuring immediate visual feedback when users mark items read, unread, or archived. - React components consume the store via
observer()HOCs frommobx-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, 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/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 handles all client-side API interactions.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →