Documentation

Notifications

Notification types

Reference for buildNotificationType in VitNode. Every option, the arguments of present, how output is cleaned, grouping, schema versions and mandatory types.

A notification type describes one kind of notification: the data it stores, how it reads in the inbox, who may see it and what members get by default. You build it with buildNotificationType() and register it on your API plugin. For a guided first type, see Publish notifications.

Signature

import { buildNotificationType } from '@vitnode/core/api/lib/notifications/registry'

buildNotificationType<TData>(
  definition: NotificationTypeDefinition<TData>,
): NotificationTypeDefinition<TData>

It returns the definition unchanged after checking it. Pass that same object to notificationTypes and to publish({ type }), so TypeScript checks data against schema at every call site.

Register types on buildApiPlugin({ notificationTypes }), or on any module with buildModule({ notificationTypes }). Core collects them from every installed plugin when the API starts.

Options

Prop

Type

buildNotificationType() throws a NotificationRegistryError when the module loads if the id, category or subject type is invalid, version is below 1, grouping.windowMinutes is outside 1 to 10,080 (one week), or defaults.email is not "none" while email is missing. Two registered types with the same id stop the API from starting.

present arguments

present runs on the API each time an item is listed or emailed. It receives:

Prop

Type

A missing label translation shows the type id instead. Category names are looked up as core.notifications.categories.<category>, then <pluginId>.notifications.categories.<category>.

How core cleans present output

Core cleans what present returns before anyone sees it:

  • title and body are plain text. Markup is never rendered, whitespace is collapsed and each string is cut at 500 characters.
  • target must be a path on the same site, such as /forum/topics/42. Absolute URLs, //host, javascript: and backslashes are dropped, and the item renders without a link.
  • If present throws or returns an empty title, the item shows "This notification is no longer available." and the error is logged.

access

Core calls access once per delivery batch, again before each email or digest, and each time a member's inbox loads. It receives a bounded batch of user ids, never the whole audience. A worked example is in Limit who sees a notification.

grouping

plugins/forum/src/api/lib/notifications.ts
grouping: {
  windowMinutes: 60,
  key: ({ data }) => `topic:${data.topicId}`,
},

Notifications with the same type and key, for the same member, in the same window join one inbox item, such as "Alex and 4 others replied to Shipping day". Windows are fixed UTC slots: with 60 minutes, 14:00 to 14:59 is one window. The key defaults to the subject. A notification without a subject, or a key that returns null, is never grouped. Use actors, actorCount and eventCount in present to word a grouped item:

plugins/forum/src/locales/api/en.json
"title": "{others, plural, =0 {{name} replied} one {{name} and # other replied} other {{name} and # others replied}} to {title}"

version and migrate

Stored notifications outlive your code. When schema changes shape, increase version and make migrate upgrade the old data. Here version 1 stored title, and version 2 renames it to topicTitle:

plugins/forum/src/api/lib/notifications.ts
export const topicReplyNotification = buildNotificationType({
  id: 'forum.topic_reply',
  version: 2,
  schema: z.object({ topicId: z.number(), topicTitle: z.string() }),
  migrate: (data, fromVersion) => {
    if (fromVersion !== 1) return data
    const old = data as { title: string; topicId: number }

    return { topicId: old.topicId, topicTitle: old.title }
  },
  // other options unchanged
})

Data that cannot be upgraded is never shown. That covers a missing migrate, a stored version newer than the code, a thrown error or a failed parse. A notification not yet delivered is marked failed, and inbox items show the "no longer available" placeholder.

mandatory

Set mandatory: true for account notices members must not miss, such as "Your role changed". The type always reaches the inbox, ignores AdminCP policies that would disable it, and shows as locked on the preferences page. Its email follows the installation default.

Password resets, email verification and sign-in codes are not notifications. Keep sending them with c.get("email").send(), so they arrive even for members who turned every notification off.