CP Notifications
The yStore CP notification system delivers real-time in-app alerts to administrators directly in the Craft Control Panel. Notifications are distinct from customer-facing emails: they are internal admin messages that appear in the bell-icon header dropdown and on the dedicated notifications index page.
What the notifications system does
- Automatically creates CP alerts for key commerce events (new orders, customer registrations, order status changes)
- Stores all notifications in a persistent database table scoped by site and optionally by user
- Shows unread count in the CP header badge
- Provides a dropdown with the five most recent notifications and a "mark all read" action
- Automatically purges notifications older than 90 days
- Provides a full paginated notifications index page at
yui/notificationswith store-level filtering - Exposes JSON controller endpoints for mark-as-read and header refresh
- Exposes a PHP service API and a Flow Manager action/trigger pair for custom automation
Auto-generated notifications
yStore registers event listeners at boot and automatically creates CP notifications for the following events.
| Event | Type | Severity | Title pattern |
|---|---|---|---|
Successful checkout (EVENT_AFTER_PURCHASE_SUCCESS) | order | info | New order #<incrementId> |
Customer sign-up (EVENT_AFTER_CUSTOMER_SIGN_UP) | customer | info | New customer registered |
Order status change (EVENT_AFTER_ORDER_STATUS_CHANGE) | order | info | Order #<orderId> status changed |
Auto-generated notifications link directly to the relevant CP page (order detail or customer record). They are broadcast to all admins (no userId target).
Notifications index page
Navigate to yStore → Notifications (yui/notifications) to see a paginated list of all CP notifications.
- Pagination: 20 notifications per page; older entries accessible via page query parameter.
- Store filter: A store-code query parameter (
?store=<code>) filters notifications to those belonging to the matching site. Store badges are shown per notification row to identify origin. - Required permission:
yui:orders:view.
The header bell icon dropdown shows the five most recent notifications and loads unread count via yui/notifications/get-latest (JSON). Mark-as-read actions post to yui/notifications/mark-read or yui/notifications/mark-all-read.
Notification record fields
Each notification row in {{%yui_notifications}} has the following fields.
| Field | Type | Constraints | Description |
|---|---|---|---|
id | int | PK | Auto-increment primary key |
siteId | int | NOT NULL, FK → {{%sites}} | Site the notification belongs to; defaults to current site |
userId | int|null | FK → {{%users}} (CASCADE) | Target user ID; null = broadcast to all admins |
type | string | max 50 chars, default system | Category label: order, customer, system, flow, or any custom string |
severity | string | max 20 chars, default info | Visual urgency: info, warning, or critical |
title | string | max 255 chars | Short title shown in the header dropdown |
message | text|null | — | Optional body with additional context |
url | string|null | max 500 chars | CP URL for quick navigation when clicking the notification |
isRead | bool | NOT NULL, default false | Read/unread state |
dateCreated | datetime | NOT NULL | Creation timestamp (UTC) |
dateUpdated | datetime | NOT NULL | Last updated timestamp |
uid | string | — | Craft UID |
Indexes: (userId, isRead, dateCreated), (siteId, dateCreated), (dateCreated) — optimized for unread-count queries and paginated listing filtered by site.
Flow Manager integration
Create admin notification action
Use the Create admin notification action (create-notification) in a flow to write a notification programmatically. All string fields support Twig tokens from the flow context.
| Field | Required | Default | Description |
|---|---|---|---|
type | Yes | flow | Notification category string |
severity | Yes | info | info, warning, or critical |
title | Yes | — | Short title; Twig-rendered |
message | No | — | Optional body text; Twig-rendered |
url | No | — | CP navigation URL; Twig-rendered |
userId | No | — | Target user ID; leave empty for broadcast |
siteId | No | — | Target site ID; leave empty for current site |
Output variables: created, type, severity, title, url, userId, siteId
After a successful createNotification() call the service automatically dispatches the notification-created Flow trigger — any matching flows run asynchronously via the Craft queue. You do not need to call the trigger manually.
Example — critical stock alert:
Place this action after a product-out-of-stock trigger to escalate to the admin panel:
type: system
severity: critical
title: "{{ product.title }} is out of stock"
url: /admin/yui/products/{{ product.id }}
Notification created trigger
The Notification created trigger (notification-created) fires whenever any notification is written to the CP notification center, regardless of source. Use it to mirror important alerts into external channels (Slack, Teams, webhook).
| Field | Default | Description |
|---|---|---|
type | (empty = any) | Filter by notification type string |
severity | any | any, info, warning, or critical |
scope | any | any, broadcast (all admins), or targeted (specific user) |
Payload variables: notificationId, type, severity, title, message, url, siteId, userId, broadcast
Example — escalate critical notifications to Slack:
- Add
notification-createdtrigger, set severity =critical. - Add a
slack-notificationaction with the message body using{{ title }}and{{ message }}.
Email style theming
The email style system controls the global visual layout of all customer-facing emails (not CP notifications). It is separate from email template content.
Built-in styles
| Style key | Description |
|---|---|
default | Gradient header, rounded cards — the standard yStore look |
minimal | Flat, clean layout with no gradients and minimal decoration |
bold | Larger typography, stronger visuals, vivid colours |
The active style is selected per-template via the Style override field in yStore → Marketing → Email Templates.
Registering a custom email style
Modules and plugins can register additional styles or override the CSS of existing built-in styles by listening to EmailStyleService::EVENT_REGISTER_EMAIL_STYLES.
use yui\craft\events\RegisterEmailStyleEvent;
use yui\craft\services\EmailStyleService;
use yui\craft\support\EventManager;
EventManager::listen(
EmailStyleService::class,
EmailStyleService::EVENT_REGISTER_EMAIL_STYLES,
function (RegisterEmailStyleEvent $event): void {
// Register a new style
$event->styles['corporate'] = [
'label' => 'Corporate',
'description' => 'A clean corporate email layout',
'css' => '@mymodule/assets/email/corporate.min.css',
'template' => 'my-module/email/themes/corporate',
];
// Override only the CSS of the built-in default style
$event->styleOverrides['default'] = [
'css' => '@mymodule/assets/email/default-override.min.css',
];
}
);
$event->styles keys:
| Key | Type | Required | Description |
|---|---|---|---|
label | string | Yes | Human-readable name shown in settings dropdowns |
description | string | No | Short description shown in the style picker |
css | string | Yes | Yii alias path to the compiled CSS file |
template | string | No | Twig template path for a custom layout; omit to use the built-in theme wrapper |
builtIn | bool | No | Reserved for core styles; do not set in third-party code |
$event->styleOverrides accepts the same css and template keys for any existing style key, including core ones. Overrides take precedence over the base style definition.
Access the service at runtime:
$emailStyles = \yui\craft\Plugin::getInstance()->getEmailStyles();
// List all registered styles (built-in + custom)
$styles = $emailStyles->getAllStyles();
// Get the CSS path for a given style key
$cssPath = $emailStyles->getStyleCssPath('corporate');
// Get the Twig layout path
$templatePath = $emailStyles->getStyleTemplatePath('corporate');
// Check whether a style key exists
if ($emailStyles->styleExists('corporate')) { ... }
Registering additional notification types
The notification preferences page (yui-core/notifications/preferences) lets admins opt in or out of individual notification types, grouped by the plugin that registers them. A type only appears there if a plugin registers it. craft-core defines and fires the EVENT_REGISTER_NOTIFICATION_TYPES event as an extension point, but does not register any notification types itself through it — registration is left entirely to other plugins/modules that listen on NotificationService::EVENT_REGISTER_NOTIFICATION_TYPES.
Listen to NotificationService::EVENT_REGISTER_NOTIFICATION_TYPES and push a NotificationType instance onto $event->types:
use Craft;
use yii\base\Event;
use yui\craftcore\events\RegisterNotificationTypesEvent;
use yui\craftcore\models\NotificationType;
use yui\craftcore\services\NotificationService;
Event::on(
NotificationService::class,
NotificationService::EVENT_REGISTER_NOTIFICATION_TYPES,
function (RegisterNotificationTypesEvent $event): void {
$event->types[] = new NotificationType([
'handle' => 'invoice.created',
'severity' => 'info',
'label' => Craft::t('superfaktura', 'Invoice created'),
'source' => 'superfaktura',
]);
}
);
NotificationType fields:
| Field | Type | Default | Description |
|---|---|---|---|
handle | string | '' | Unique identifier for this type within source; combined with source (source::handle) as the preference storage key |
source | string | '' | Plugin/module identifier; also groups types under the same provider tab on the preferences page |
label | string | '' | Human-readable name shown in the preferences list; translate with Craft::t() |
severity | string | 'info' | info, warning, or critical |
group | string | '' | Sub-section label within the provider's tab; types with no group fall under General |
description | string | '' | Optional short helper text shown under the label |
providerLabel | string | '' | Human-readable provider tab name; falls back to ucfirst(source) when empty |
NotificationService::getRegisteredTypes() normalizes legacy listeners that push a plain array with a type key instead of a handle key, so older third-party registrations keep working, but new code should always use NotificationType with handle.
Registered types are grouped first by source/providerLabel (provider tabs), then by group (collapsible sections within a tab), and rendered as individually toggleable checkboxes on the preferences page. Registering a type does not create notifications by itself — it only makes an existing type/source pair (used with createNotification()) visible and toggleable here.
createNotification() skips creating a targeted ($userId-scoped) notification if the user has opted out of that source/type pair (or the whole source). Already-stored notifications — including broadcast ones (userId = null) — are only filtered out of getUnreadCount() and getLatestNotifications() when the entire source is opted out; a per-type opt-out does not retroactively hide broadcast notifications that were already created for that type.
PHP developer API
NotificationService implements NotificationServiceInterface and is registered as the notification component. Access it through the plugin:
$notifications = \yui\craft\Plugin::getInstance()->getNotification();
createNotification()
public function createNotification(
string $type,
string $severity,
string $title,
?string $message = null,
?string $url = null,
?int $siteId = null,
?int $userId = null,
): bool
Creates a new notification record and automatically dispatches the notification-created Flow trigger. Returns true on success.
| Parameter | Constraints | Description |
|---|---|---|
$type | max 50 chars | Category string: order, customer, system, flow, or any custom value |
$severity | max 20 chars | info, warning, or critical |
$title | max 255 chars | Short title displayed in the header dropdown |
$message | — | Optional additional context |
$url | max 500 chars | Optional CP URL for the notification link |
$siteId | — | Site scope; defaults to the current site when null |
$userId | — | Target user; null broadcasts to all admins |
getUnreadCount(int $userId): int
Returns the unread notification count for a given user. Includes both broadcast notifications (userId = null) and those targeted directly to the user.
getLatestNotifications(int $userId, int $limit = 5): array
Returns the most recent notifications for a user as a plain array of records, ordered newest-first. Used by the CP header dropdown.
getAllNotifications(int $userId, int $page = 1, int $perPage = 20, ?int $siteId = null): array
Returns a paginated result set for the notifications index page. The return value is an array with the following keys:
| Key | Type | Description |
|---|---|---|
notifications | array | Notification rows for the current page |
total | int | Total matching notification count |
page | int | Current page number |
perPage | int | Items per page |
totalPages | int | Total page count |
Pass $siteId to filter notifications to a specific site. null returns notifications for all sites.
markAsRead(int $id, int $userId): bool
Marks a single notification as read. Only succeeds if the notification belongs to the user or is a broadcast notification.
markAllAsRead(int $userId): int
Marks all unread notifications for a user as read. Returns the number of rows updated.
deleteOlderThan(int $days = 90): int
Deletes notifications older than the given number of days. The plugin runs this automatically during a daily maintenance task. Returns the number of deleted rows.
Replacing the service
The service is bound to the notification component via NotificationServiceInterface. You can swap it in a module:
use yui\craft\Plugin;
use yui\craft\contracts\NotificationServiceInterface;
// In your module's init()
Plugin::getInstance()->set('notification', MyCustomNotificationService::class);
Your class must implement NotificationServiceInterface.
Audience check
| Audience | Covered |
|---|---|
| Customer / storefront | Not applicable — CP notifications are internal admin-only; the customer account activity stub is not yet a functional feature in v2.2.1 |
| Admin / Craft CP | Yes — bell icon header dropdown, paginated notifications index (yui/notifications), unread badge, store filter, auto-generated events, Flow action |
| Developer / integrator | Yes — NotificationService PHP API, NotificationServiceInterface for replacement, NotificationEventListener for auto-wiring custom events, email style RegisterEmailStyleEvent, notification type registration via EVENT_REGISTER_NOTIFICATION_TYPES, Flow create-notification action + notification-created trigger |