How to Set Up Push Notifications for Chat and System Events in AnythingLLM
AnythingLLM supports real-time Web Push notifications for chat messages and system alerts through a built-in service that automatically generates VAPID keys, manages browser subscriptions via a service worker, and delivers targeted payloads to single-user or multi-user environments.
The Mintplex-Labs/anything-llm repository includes a complete push notification infrastructure that works out of the box for both self-hosted single-user instances and multi-user deployments. This guide covers the server configuration, client integration, and programmatic usage of the web-push implementation found in the core source code.
How Push Notifications Work in AnythingLLM
The architecture consists of three coordinated layers that handle key generation, subscription management, and message delivery.
- Server Layer: Located in
server/utils/PushNotifications/index.js, this manages VAPID key generation, subscription persistence (file-based for single-user or database for multi-user), and theweb-pushlibrary integration. - API Layer: Defined in
server/endpoints/webPush.js, this exposes the public key endpoint and subscription registration route. - Client Layer: The React hook in
frontend/src/hooks/useWebPushNotifications.jshandles permission requests, service worker registration, and subscription posting, whilefrontend/public/service-workers/push-notifications.jsdisplays native browser notifications.
The boot process in server/utils/boot/index.js automatically initializes the service. Both bootHTTP (line 39) and bootSSL (line 71) call await PushNotifications.setupPushNotificationService() to ensure VAPID keys exist and subscriptions load on startup.
Server-Side Configuration
Automatic VAPID Key Generation
You do not need to manually create VAPID keys. During the first server start, setupPushNotificationService() checks for storage/push-notifications/vapid-keys.json. If absent, it executes:
const vapidKeys = webpush.generateVAPIDKeys();
fs.writeFileSync(
path.resolve(this.storagePath, `vapid-keys.json`),
JSON.stringify(vapidKeys, null, 2)
);
These keys persist to the storage directory and are reused on subsequent boots.
Storage Modes
- Single-User Mode: Subscriptions save to
storage/push-notifications/primary-subscription.jsonusing the key"primary". - Multi-User Mode: Subscriptions store in the
userstable under individual user records, allowing targeted notifications per user ID.
API Endpoints for Web Push
The server exposes two REST endpoints in server/endpoints/webPush.js:
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /web-push/pubkey |
Returns the VAPID public key required for browser subscription |
| POST | /web-push/subscribe |
Accepts and stores the Web Push subscription object |
The client automatically interacts with these endpoints through the useWebPushNotifications hook.
Client-Side Integration
Implementing the React Hook
Import and invoke the hook in your root component (e.g., frontend/src/App.jsx) to enable automatic subscription:
import useWebPushNotifications from "@/hooks/useWebPushNotifications";
function App() {
useWebPushNotifications(); // Auto-registers on mount
return (
<div>{/* Your application UI */}</div>
);
}
export default App;
Subscription Flow
The subscribeToPushNotifications() function (lines 24–100) performs the following sequence:
- Validates browser support for Service Workers and the Push API
- Requests
Notificationpermission from the user - Fetches the VAPID public key from
/web-push/pubkey - Registers the service worker at
/service-workers/push-notifications.js?v=${SW_VERSION}(current version"1.0.0") - Calls
pushManager.subscribe()with the server key - POSTs the resulting subscription to
/web-push/subscribe
Service Worker Behavior
The worker at frontend/public/service-workers/push-notifications.js listens for push events:
self.addEventListener('push', event => {
const payload = event.data?.json();
if (!payload) return;
self.registration.showNotification(payload.title || 'AnythingLLM', {
...payload,
icon: '/favicon.png',
});
});
self.addEventListener('notificationclick', event => {
event.notification.close();
const { onClickUrl } = event.notification.data || {};
if (onClickUrl) event.waitUntil(clients.openWindow(onClickUrl));
});
Clicking a notification opens the URL specified in data.onClickUrl.
Sending Notifications Programmatically
Use the exported pushNotificationService to deliver messages from anywhere in your server code:
const { pushNotificationService } = require("./utils/PushNotifications");
const payload = {
title: "New chat message",
body: "Alice replied in thread #1234",
data: { onClickUrl: "/chat/1234" },
// Optional: actions, image, badge
};
// Single-user mode
pushNotificationService.sendNotification({
to: "primary",
payload,
});
// Multi-user mode - target specific user
pushNotificationService.sendNotification({
to: userId, // Database user ID
payload,
});
The sendNotification method (lines 175–186) validates the subscription, logs the action, and delegates to the web-push library for delivery.
Example: Chat Message Notification
Create a utility function to notify users of new messages:
// server/utils/notifyChat.js
const { pushNotificationService } = require("./PushNotifications");
async function notifyChatMessage({ chatId, messageText, authorName, recipientUserId }) {
const payload = {
title: `New message from ${authorName}`,
body: messageText.substring(0, 100), // Truncate long messages
data: { onClickUrl: `/workspace/chat/${chatId}` },
};
// Target specific user in multi-user instances
await pushNotificationService.sendNotification({
to: recipientUserId || "primary",
payload,
});
}
module.exports = { notifyChatMessage };
Summary
- VAPID keys generate automatically on first boot and store in
storage/push-notifications/vapid-keys.json - Two API endpoints (
/web-push/pubkeyand/web-push/subscribe) handle the Web Push protocol handshake - Single-user subscriptions persist to JSON files; multi-user subscriptions save to the database
userstable - Client integration requires only importing
useWebPushNotificationsin your root React component - Service worker at
/service-workers/push-notifications.jsdisplays notifications and handles click events vianotificationclick - Server-side sending uses
pushNotificationService.sendNotification({ to, payload })with target"primary"or a specific user ID
Frequently Asked Questions
Do I need to manually generate VAPID keys for AnythingLLM push notifications?
No. The server automatically generates and persists VAPID keys during the boot sequence when you call setupPushNotificationService(). The keys are stored in storage/push-notifications/vapid-keys.json and reused on subsequent starts.
How do I target specific users in multi-user mode?
In multi-user deployments, store the subscription under the user's database record by ensuring the user is authenticated when calling /web-push/subscribe. Then use pushNotificationService.sendNotification({ to: userId, payload }) where userId matches the database record identifier.
Where are subscriptions stored in single-user mode?
Single-user instances save subscriptions to storage/push-notifications/primary-subscription.json. The system uses the constant string "primary" as the identifier in the to parameter when sending notifications.
What triggers the service worker to update?
The service worker URL includes a version query parameter (?v=1.0.0). Bump the SW_VERSION constant in the client code to force browsers to register the new worker when the application deploys.
Can I customize the notification appearance and behavior?
Yes. The payload object accepts standard Web Notification API properties including title, body, icon, image, badge, actions, and data. The service worker passes these directly to showNotification(), and the notificationclick handler respects data.onClickUrl for navigation.
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 →