Categories and Subjects
Overview
Every notification is filed under a subject, and every subject belongs to a category. That two-level taxonomy is what the notification settings screen groups by, and what user preferences are keyed on: a preference entry exists per categoryId:subjectId pair.
You declare categories and subjects by calling NotificationCategory() and NotificationSubject(). Both calls register the declaration with the CMS and return the info object, which you keep as a module-level constant. You pass the category constant to NotificationSubject(), and the subject constant to the builder's .subject() when you send.
Declarations are values, not decorators: they run when the module that contains them is imported. Declare them at the top level of a module that is imported during startup, so the taxonomy is complete before the first notification is sent.
Declaring a Category
NotificationCategory(id, category) registers a category and returns the resulting NotificationCategoryInfo.
import { NotificationCategory } from "@antelopejs-private/cms/interfaces/cms-notifications";
export const shopCategory = NotificationCategory("shop", {
labelKey: "shop.notifications.categories.shop",
descriptionKey: "shop.notifications.categories.shop_desc",
icon: "i-ph-shopping-cart",
});
| Parameter | Description |
|---|---|
id | Identifier of the category. It becomes the first half of every preference key under this category. |
category | The rest of the NotificationCategoryInfo object — every field except id. |
NotificationCategoryInfo
| Option | Type | Description |
|---|---|---|
id | string | Category identifier. Supplied through the first argument of NotificationCategory(). |
labelKey | string | Translation key for the category name. |
descriptionKey | string (optional) | Translation key for a longer description. |
icon | string | Icon name, in the icon syntax used across the dashboard (for example i-ph-shopping-cart). |
togglePermission | TogglePermission (optional) | Parent toggle stance applied when evaluating this category's subjects. It does not lock the category's own switch. |
Registering a second category with an id that is already taken is ignored: the first declaration wins.
Declaring a Subject
NotificationSubject(id, subject) registers a subject under an already-declared category and returns the resulting NotificationSubjectInfo.
import {
NotificationCategory,
NotificationSubject,
} from "@antelopejs-private/cms/interfaces/cms-notifications";
export const shopCategory = NotificationCategory("shop", {
labelKey: "shop.notifications.categories.shop",
icon: "i-ph-shopping-cart",
});
export const orderSubject = NotificationSubject("orders", {
category: shopCategory,
labelKey: "shop.notifications.subjects.orders",
descriptionKey: "shop.notifications.subjects.orders_desc",
togglePermission: "default",
});
export const stockSubject = NotificationSubject("stock", {
category: shopCategory,
labelKey: "shop.notifications.subjects.stock",
togglePermission: "allowed",
});
| Parameter | Description |
|---|---|
id | Identifier of the subject. It is unique within its category, not globally: the preference key is categoryId:subjectId. |
subject | The rest of the NotificationSubjectInfo object — every field except id. |
NotificationSubjectInfo
| Option | Type | Description |
|---|---|---|
id | string | Subject identifier, unique within the category. Supplied through the first argument of NotificationSubject(). |
category | NotificationCategoryInfo | The category this subject belongs to — the value returned by NotificationCategory(). |
labelKey | string | Translation key for the subject name. |
descriptionKey | string (optional) | Translation key for a longer description. |
togglePermission | TogglePermission (optional) | Whether users may turn this subject off, read together with the category's value. |
A subject carries the whole category object, not just its id, so subject.category.id is always reachable from a subject you hold. That is how a send resolves the preference key from nothing but the subject you passed to the builder.
Registering a second subject with an id already taken inside the same category is ignored, the same way categories are.
Declaration Order
The category has to be registered before any of its subjects: a subject whose category is not registered yet is rejected and never enters the registry, so nothing sent against it is delivered. Registrations run in call order, so declaring the category above its subjects in the same file — as in the example above — is enough.
Registrations are scoped to the module that made them: when that module is unloaded, its categories and subjects are unregistered.
Toggle Permissions
TogglePermission controls whether a user is allowed to opt out of a subject from the notification settings screen.
type TogglePermission = "allowed" | "forbidden" | "default";
The field is optional on both NotificationCategoryInfo and NotificationSubjectInfo, and the settings screen locks a subject's switch only when both of these hold:
- the subject declares
togglePermission: "forbidden", and - its category declares
togglePermissionas"default"or"forbidden".
Every other combination leaves the subject toggleable. A category's own switch is never locked: the value you put on a category acts as the parent half of the rule above and nothing else.
| Value | Meaning on a subject | Meaning on a category |
|---|---|---|
"forbidden" | The user may not turn the subject off, provided the category declares "default" or "forbidden". | Enables the subject-side lock. The category itself stays toggleable. |
"default" | The subject stays toggleable. | Enables the subject-side lock, same as "forbidden". |
"allowed" | The subject stays toggleable, same as "default". | Leaves every subject toggleable, same as omitting the field. |
So for a notification a user must always receive, declare togglePermission: "forbidden" on the subject and togglePermission: "default" on the category that holds it. SystemCategory declares "default", which is what makes GeneralSubject mandatory.
import {
NotificationCategory,
NotificationSubject,
} from "@antelopejs-private/cms/interfaces/cms-notifications";
export const alertCategory = NotificationCategory("alerts", {
labelKey: "shop.notifications.categories.alerts",
icon: "i-ph-warning",
togglePermission: "default", // required for the forbidden subject below to lock
});
export const outageSubject = NotificationSubject("outage", {
category: alertCategory,
labelKey: "shop.notifications.subjects.outage",
togglePermission: "forbidden",
});
togglePermission only governs what the settings screen offers. It does not change what .toUser() and the other dispatch methods do with a preference the user has already stored — see Delivery Rules.
Read Scope
ReadScope describes how the read state of a multi-recipient notification behaves.
type ReadScope = "individual" | "shared";
| Value | Meaning |
|---|---|
"individual" | Each recipient gets their own copy with its own read state. This is the default. |
"shared" | The copies are grouped: once one recipient reads the notification, it counts as read for the whole group. |
The scope is chosen per send, not per subject; you pass it through SendOptions on the multi-recipient dispatch methods. See Send Options.
Built-in Categories and Subjects
The CMS declares one category and five subjects of its own. Import them and pass them to .subject() when your notification fits one of them, instead of declaring a near-duplicate.
import {
AccountSubject,
AutomationSubject,
CollaborationSubject,
GeneralSubject,
SecuritySubject,
SystemCategory,
} from "@antelopejs-private/cms/interfaces/cms-notifications";
SystemCategory is the category all five subjects belong to. Its id is system, its icon is i-ph-gear, and its togglePermission is "default" — the parent value that lets GeneralSubject's "forbidden" take effect.
| Constant | Id | Toggle permission | Used for |
|---|---|---|---|
GeneralSubject | general | "forbidden" | Unclassified platform messages. Users cannot opt out. |
SecuritySubject | security | "default" | Sign-ins, password and email changes, two-factor changes, backup-code regeneration. |
AccountSubject | account | "default" | Account lifecycle messages, such as the welcome notification. |
CollaborationSubject | collaboration | "default" | Messages about other people in the workspace, such as a collaborator joining. |
AutomationSubject | automation | "default" | Messages emitted by automations. |
Their labels and descriptions use the cms.notifications.* translation keys shipped with the CMS.
Importing the Types
The declaration functions and the built-in constants are exported from the interface root. The type definitions live in the types subpath:
| Symbol | Import from |
|---|---|
NotificationCategory, NotificationSubject, SystemCategory, GeneralSubject, SecuritySubject, AccountSubject, CollaborationSubject, AutomationSubject | @antelopejs-private/cms/interfaces/cms-notifications |
NotificationCategoryInfo, NotificationSubjectInfo, TogglePermission, ReadScope | @antelopejs-private/cms/interfaces/cms-notifications/types |
import type { NotificationSubjectInfo } from "@antelopejs-private/cms/interfaces/cms-notifications/types";
interface NotificationTemplate {
icon: string;
subject: NotificationSubjectInfo;
linkTo: string;
}
Typing your own helpers against NotificationSubjectInfo is the usual reason to reach for the types subpath: it lets a template table or a helper function accept any subject, built-in or your own.
Next Steps
- Sending Notifications - Build a notification against a subject and dispatch it to users, roles, or everyone