diff --git a/.changeset/swappable-notification-description.md b/.changeset/swappable-notification-description.md
new file mode 100644
index 0000000000..abfa470035
--- /dev/null
+++ b/.changeset/swappable-notification-description.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-notifications': patch
+---
+
+The notification description used in the notifications table is now a swappable component, so that apps can replace its rendering with a custom implementation.
diff --git a/plugins/notifications/report.api.md b/plugins/notifications/report.api.md
index ff39a03a8a..627ea51e82 100644
--- a/plugins/notifications/report.api.md
+++ b/plugins/notifications/report.api.md
@@ -14,6 +14,7 @@ import { NotificationSettings } from '@backstage/plugin-notifications-common';
import { NotificationSeverity } from '@backstage/plugin-notifications-common';
import { NotificationStatus } from '@backstage/plugin-notifications-common';
import { RouteRef } from '@backstage/core-plugin-api';
+import { SwappableComponentRef } from '@backstage/frontend-plugin-api';
import { TableProps } from '@backstage/core-components';
import { TranslationRef } from '@backstage/frontend-plugin-api';
@@ -49,6 +50,20 @@ export type GetTopicsResponse = {
topics: string[];
};
+// @public
+export const NotificationDescription: {
+ (props: NotificationDescriptionProps): JSX.Element | null;
+ ref: SwappableComponentRef<
+ NotificationDescriptionProps,
+ NotificationDescriptionProps
+ >;
+};
+
+// @public
+export interface NotificationDescriptionProps {
+ description: string;
+}
+
// @public (undocumented)
export interface NotificationsApi {
// (undocumented)
diff --git a/plugins/notifications/src/components/NotificationsTable/DefaultNotificationDescription.tsx b/plugins/notifications/src/components/NotificationsTable/DefaultNotificationDescription.tsx
new file mode 100644
index 0000000000..9524937380
--- /dev/null
+++ b/plugins/notifications/src/components/NotificationsTable/DefaultNotificationDescription.tsx
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { Text, Button } from '@backstage/ui';
+import { useState } from 'react';
+import type { NotificationDescriptionProps } from './NotificationDescription';
+
+const MAX_LENGTH = 100;
+
+export const DefaultNotificationDescription = (
+ props: NotificationDescriptionProps,
+) => {
+ const { description } = props;
+ const [shown, setShown] = useState(false);
+ const isLong = description.length > MAX_LENGTH;
+
+ if (!isLong) {
+ return {description};
+ }
+
+ if (shown) {
+ return (
+
+ {description}{' '}
+
+
+ );
+ }
+ return (
+
+ {description.substring(0, MAX_LENGTH)}...{' '}
+
+
+ );
+};
diff --git a/plugins/notifications/src/components/NotificationsTable/NotificationDescription.tsx b/plugins/notifications/src/components/NotificationsTable/NotificationDescription.tsx
index 760473d064..c284e1e85a 100644
--- a/plugins/notifications/src/components/NotificationsTable/NotificationDescription.tsx
+++ b/plugins/notifications/src/components/NotificationsTable/NotificationDescription.tsx
@@ -13,46 +13,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { Text, Button } from '@backstage/ui';
-import { useState } from 'react';
+import { createSwappableComponent } from '@backstage/frontend-plugin-api';
-const MAX_LENGTH = 100;
+/**
+ * Props for the {@link NotificationDescription} swappable component.
+ *
+ * @public
+ */
+export interface NotificationDescriptionProps {
+ /**
+ * The plain-text description of the notification.
+ */
+ description: string;
+}
-export const NotificationDescription = (props: { description: string }) => {
- const { description } = props;
- const [shown, setShown] = useState(false);
- const isLong = description.length > MAX_LENGTH;
-
- if (!isLong) {
- return {description};
- }
-
- if (shown) {
- return (
-
- {description}{' '}
-
-
- );
- }
- return (
-
- {description.substring(0, MAX_LENGTH)}...{' '}
-
-
- );
-};
+/**
+ * Swappable component that renders the description of a notification in the
+ * notifications table. Apps can override this to customize how descriptions
+ * are displayed.
+ *
+ * @public
+ */
+export const NotificationDescription =
+ createSwappableComponent({
+ id: 'notifications.notification-description',
+ loader: () =>
+ import('./DefaultNotificationDescription').then(
+ m => m.DefaultNotificationDescription,
+ ),
+ });
diff --git a/plugins/notifications/src/components/NotificationsTable/index.ts b/plugins/notifications/src/components/NotificationsTable/index.ts
index a1fb7a25c6..81d6cacce9 100644
--- a/plugins/notifications/src/components/NotificationsTable/index.ts
+++ b/plugins/notifications/src/components/NotificationsTable/index.ts
@@ -14,3 +14,4 @@
* limitations under the License.
*/
export * from './NotificationsTable';
+export * from './NotificationDescription';