Backstage Notifications

Keeping your users up-to-date

Heikki Hellgren
6 min readApr 29, 2024
Backstage Logo © Spotify

Backstage is an open platform for building developer portals for companies with software assets.

This post is about notifications, a new feature in the Backstage core introduced in CNCF KubeCon24. It has been stable since the Backstage release 1.26.0 but is still missing some features to make it complete. Most of the features this far have been developed by me, and Marek Libra from Red Hat, big thanks to him for this cooperation! (Not to forget the great Backstage core team who have been giving good architectural direction and code reviews!)

This post introduces ways you can take notifications in use in your Backstage application, how you can integrate your own plugins to utilize the notifications, and what is on the roadmap for the future.

Adding notifications to your app

Notifications consist of four different plugins; notifications (frontend), notifications-backend, notifications-common, and notifications-node. For notifications to work in your app, you first need to install the notifications frontend and backend plugins (see linked README files). Please note that the backend plugin only supports the new Backstage backend system and if you still need to migrate, you should do that first. To add support for real-time updates, you also need to install the signals frontend and backend plugins.

Next, add the NotificationsSidebarItemto your main menu and configure routing to the NotificationsPage as follows:

// packages/app/src/App.tsx
const routes = (
<FlatRoutes>
// ... Your other routes
<Route path="/notifications" element={<NotificationsPage />} />
</FlatRoutes>
)

// packages/app/src/components/Root/Root.tsx
<NotificationsSidebarItem
webNotificationsEnabled // Enables WebNotification API
titleCounterEnabled // Enables counter in page title
snackbarEnabled={false} // Disables snackbar notifications
/>

From the UI point of view, by default, the upcoming snack bars and title notification counter are enabled. You can additionally enable support for web notifications that show the Backstage notifications in the OS notification center or disable any of the default features if they are not to your liking by passing configuration as properties to the sidebar item.

Example view of notifications

Now you are all set up for your application users to receive notifications from different plugins and mark them as read, follow links in the notifications, and save them for later. One plugin that already has this integration is my Q&A plugin and I hope more will follow shortly.

Integrating your plugins

To send notifications from your backend plugins, you must integrate into the NotificationService from the notifications-nodepackage. This service has a method called sendthat supports a couple of different ways to send notifications:

  • Broadcast notifications are sent to all users of your application 📢
  • Entity notifications are sent to entities. This works for any kind of entity as the users receiving the notification are resolved by the notification backend; sending a notification to a group will send it to all members of that group while sending a notification to a, let’s say system, will send the notification to the owner of that system

Beyond selecting the notification recipients, there are a couple more interesting features you might find handy:

  • Severity can be used to determine how important the notification is. By default, all notifications are sent as ‘normal’ severity. For the time being, this only affects the way notifications are rendered but may impact also the way notifications are handled later on
  • Scope can be used to re-use existing notifications with the same scope. This means that if you have already sent a notification with some scope and the user has marked the notification read, sending a new notification with the same scope will re-notify the user about the same thing and mark the notification as unread

To send a notification from the backend plugin:

// Add the notification service as a dependency to your plugin
// plugins/my-plugin/plugin.ts:
import { notificationService } from '@backstage/plugin-notifications-node';
export const myPlugin = createBackendPlugin({
pluginId: 'my-plugin',
register(env) {
env.registerInit({
deps: {
httpRouter: coreServices.httpRouter,
notifications: notificationService,
},
async init({
httpRouter,
notifications,
}) {
httpRouter.use(
await createRouter({
notifications,
}),
);
},
});
},
});

// And send it in the router
export async function createRouter(
options: RouterOptions,
): Promise<express.Router> {
const router = Router();
router.get('/test', async(_request, _response) => {
try {
await this.notifications.send({
recipients: { type: 'entity', entityRef: 'user:default/john.doe' },
// OR: recipients: { type: 'broadcast' },
payload: {
title: 'Hello John',
description: 'This is a test notification',
link: `/catalog/default/user/${user.name}`,
topic: 'Test',
severity: 'high',
scope: 'Test',
},
});
} catch (e) {
this.logger.error(`Failed to send notification for testing: ${e}`);
}
});
return router;
}

❗Please note that at the moment, notification sending may throw an exception so handle it with care.

Architecture overview

Architecture overview

Notifications are sent from other backend plugins by using the NotificationServicethat sends the payload to the notification backend for handling. The backend again uses the SignalServiceto deliver the notification to users. Signals can be used to send data from a backend plugin to the frontend using WebSockets and under the hood it uses the EventsServicefor delivery. The default EventsService lives in memory so if you have multiple hosts, you have to come up with your way of delivering events between hosts or use some of the existing solutions like the AWS SQS events module. Also, please note that signal and notifications backend must be installed in all hosts for the system to work.

The separation between Signals and Notifications was pretty clear from the beginning as Signals can also be used for other purposes than notifications to communicate changes near real-time from the backend to the frontend without the need for polling. Signals use a single WebSocket connection per client for delivery by multiplexing the sent messages. This works by clients subscribing to different channels and Signals backend handling the message sending and connection lifecycle.

You can utilize signals alone in your plugins. This requires a dependency in your backend plugin to the signals-nodeplugin that offers you a SignalService. On the frontend, you can utilize the useSignal hook to receive signals from a specific channel:

// In the backend:
import { SignalsService } from '@backstage/plugin-signals-node';

signalService.publish<MySignalType>({
recipients: { type: 'broadcast' },
// OR: recipients: { type: 'user', entityRef: ['user:default/john.doe'] }
channel: 'myPlugin:update',
message: {
title: 'My message title',
},
});

// In the frontend:
import { useSignal } from '@backstage/plugin-signals-react';

const { lastSignal } = useSignal<MySignalType>('myPlugin:update');
useEffect(() => {
if(lastSignal) {
console.log(lastSignal);
}
}, [lastSignal]);

This hopefully allows more dynamic functionalities in the plugins and allows developing plugins like chat or monitoring that update in real-time. More examples can be found from the Q&A plugin as well as from the notifications plugins [1, 2].

On the roadmap

There are going to be a lot of new features for notifications in the upcoming 1.27.0 (or later) Backstage release. For example:

  • Notifications will now be shown in snack bars in the bottom right corner of the UI
  • Notifications can be all marked read with a single button
  • New notification processors are introduced which allow sending notifications also via email or Slack
  • Signals will support checking user permissions to allow/disallow subscribing to different channels

Beyond these there are still a lot of things upcoming later on like user-specific notification settings and hopefully integration to the Backstage core services like the scaffolder and the catalog. See more details in the notifications BEP and feel free to participate in the development and discussion!

Want to Connect?

I am Heikki Hellgren, lead developer and technology enthusiast
at OP Financial Group.

You can connect with me on LinkedIn!

--

--

Heikki Hellgren

Father of two, husband and Lead Developer @ OP Financial Group. I write about things I like and things I don’t. More info @ https://drodil.kapsi.fi