Notifications
Move from toasts to notifications
Upgrade a VitNode install to stored notifications and replace a plugin's realtime toasts with c.get("notifications").publish(), so members see them after a reload or when they come back.
Before stored notifications, a VitNode "notification" was a toast pushed over the WebSocket. It reached open tabs only and was gone after a reload. This guide upgrades your database and turns the toasts members would look for later into stored notifications. Looking for a fix instead? See Troubleshooting.
Apply the database migration
Stored notifications add six core_notification* tables. Run the migrations once after upgrading @vitnode/core:
bun run db:migrateIn development, pnpm dev runs vitnode db:prepare, which applies them for you.
Pick the toasts to keep
A reply, a mention, an approval or a finished export belongs in the inbox, because a member may look for it later. Feedback about something happening right now, such as "Import started", can stay a toast.
Declare a type for each
Declare and register one type per kept toast, as in Publish notifications:
import { buildNotificationType } from '@vitnode/core/api/lib/notifications/registry'
import { z } from 'zod'
export const exportReadyNotification = buildNotificationType({
id: 'shop.export_ready',
version: 1,
schema: z.object({ exportId: z.number() }),
category: 'system',
label: '@acme/shop.notifications.export_ready.label',
defaults: { inApp: true, email: 'none' },
present: ({ data, t }) => ({
title: t('@acme/shop.notifications.export_ready.title'),
target: `/shop/exports/${data.exportId}`,
}),
})Replace the toast with publish()
c.get('realtime').sendToUser(user.id, notificationsChannel, {
title: 'Your export is ready',
type: 'success',
})
await c.get('notifications').publish({
type: exportReadyNotification,
recipients: [user.id],
allowSelf: true,
data: { exportId: job.id },
idempotencyKey: `export:${job.id}`,
})The bell updates over the same WebSocket, so members still see it right away. allowSelf: true matters here: the member who started the export is also the request's actor, and the actor is skipped by default.
Check the result
- Run the export as a member, then reload the page. The notification is still under the bell, with an unread count.
- Sign out and back in on another browser. The same notification is there.
- On
/settings/notifications, the new type is listed and can be turned off.
The AdminCP dashboard's Send a notification widget already works this way: it shows a toast and stores the message as a core.admin_message notification. Toasts sent with sendToUser() and notificationsChannel keep working unchanged, see WebSocket.