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.

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

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:

  1. Validates browser support for Service Workers and the Push API
  2. Requests Notification permission from the user
  3. Fetches the VAPID public key from /web-push/pubkey
  4. Registers the service worker at /service-workers/push-notifications.js?v=${SW_VERSION} (current version "1.0.0")
  5. Calls pushManager.subscribe() with the server key
  6. 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/pubkey and /web-push/subscribe) handle the Web Push protocol handshake
  • Single-user subscriptions persist to JSON files; multi-user subscriptions save to the database users table
  • Client integration requires only importing useWebPushNotifications in your root React component
  • Service worker at /service-workers/push-notifications.js displays notifications and handles click events via notificationclick
  • 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →