Chapter 8. Front-end plugin wiring
You can configure front-end plugins to customize icons, integrate components at mount points, and offer or replace utility APIs.
8.1. Understanding front-end plugin wiring Copy linkLink copied to clipboard!
You can configure front-end plugins to customize icons, integrate components at mount points, and offer or replace utility APIs.
8.1.1. Front-end plugin wiring Copy linkLink copied to clipboard!
Front-end plugin wiring integrates dynamic front-end plugin components, such as new pages, UI extensions, icons, and APIs, into Red Hat Developer Hub.
Because the dynamic plugins load at runtime, the core application must discover and connect the exported assets of the plugin to the appropriate user interface systems and locations.
8.1.1.1. Understand why front-end wiring is required Copy linkLink copied to clipboard!
Because dynamic front-end plugins load their code at runtime, the Developer Hub application requires explicit instructions to integrate the plugin components in the user interface (UI).
Front-end wiring provides the metadata and instructions necessary to bridge this gap, informing the applications on how to:
-
Add new pages and routes to the main application. (using
dynamicRoutes) -
Inject custom components into existing UI pages. (using
mountPoints) -
Configure application-wide customizations, such as new icons or themes. (using
appIcons) -
Add new menu items. (using
menuItems) -
Bind to existing plugins. (using
routeBindings)
The wiring configuration, typically located in app-config.yaml or dynamic-plugins-config.yaml, gives the application the necessary metadata (including the component names, paths, and integration points) to render and use the plugin features.
8.1.1.2. Consequences of skipping front-end wiring Copy linkLink copied to clipboard!
If you skip front-end wiring, the system discovers the plugin but does not load it because front-end plugins require explicit configuration.
You can expect the following behavior when you skip front-end wiring:
- Disabled functionality
- The Backstage application cannot integrate or use the plugin exports.
- Invisible components
- New pages, sidebar links, or custom cards do not render in the application UI.
- Unregistered APIs
- Custom utility APIs or API overrides provided by the plugin are not registered in the application API system, which can cause plugins or components to fail.
- Unused assets
- Icons, translations, and themes are not registered or available for use.
If a plugin is not visible even with front-end wiring, the plugin is likely misconfigured. Troubleshoot the issue by checking the Console tab in the Developer Tools of your browser for specific error messages or warnings.
8.1.1.3. Dynamic front-end plugins for application use Copy linkLink copied to clipboard!
A dynamic front-end plugin requires front-end wiring when it exports a feature for integration into the main Backstage application UI. The following scenarios require wiring:
| Scenario | Wiring configuration | Description |
|---|---|---|
|
| Add or customize a tab on the Catalog entity view. | |
|
| Link a route in one plugin to an external route defined by another plugin. | |
|
| Supply a custom utility API implementation or override an existing one. | |
|
|
Add a new page and route to the application (for example, | |
|
| Inject custom widgets, cards, listeners, or providers into existing pages (for example, the Catalog entity page). | |
|
| Add a new entry to the main sidebar or customize its order and nesting. | |
|
| Add custom icons to the application catalog or define a new Backstage theme. | |
|
| Expose custom field extensions for the Scaffolder or new add-ons for TechDocs. | |
|
| Offer new translation files or override default plugin translations. |
8.1.1.3.1. Example of Front-end wiring workflow Copy linkLink copied to clipboard!
Front-end wiring configuration occurs in the app-config.yaml or a dedicated dynamic-plugins-config.yaml file. The dynamic plugin exposes components, routes, or APIs. For example, a module exports a plugin component.
The application administrator defines the wiring in the configuration file, using the plugin package name to register the exports, such as adding a new page with a sidebar link.
# dynamic-plugins-config.yaml
plugins:
- plugin: <plugin_path_or_url>
disabled: false
pluginConfig:
dynamicPlugins:
frontend:
my-plugin-package-name: # The plugin's unique package name
dynamicRoutes: # Wiring for a new page/route
- path: /my-new-page # The desired URL path
importName: <my-plugin>PluginPage # The exported component name
menuItem: # Wiring for the sidebar entry
icon: favorite # A registered icon name
text: My Custom Page
When the application loads, it performs the following steps:
-
It parses the
dynamic-plugins-config.yaml. -
It uses the
<plugin_path_or_url>to download the plugin bundle using the dynamic loading mechanism. -
If the package exports the plugin object, the application adds it to the list provided to the Backstage
createAppAPI, registering the plugin properly with the front-end application. It uses the configuration block (
dynamicRoutes,menuItem) to:-
Add an entry to the internal router mapping
/my-new-pageto the<my-plugin>PluginPagecomponent. -
Construct and render a new sidebar item labeled My Custom Page, pointing to the
/my-new-pageroute.
-
Add an entry to the internal router mapping
If the configuration is missing, steps 1 and 2 might still occur, but the application skips the final registration in step 3 and the wiring/rendering in step 4, and no UI changes occur.
8.2. Extend internal icon catalog Copy linkLink copied to clipboard!
You can use the internal catalog to fetch icons for configured routes with sidebar navigation menu entry.
Procedure
Add a custom icon to the internal icon catalog for use in the menu item of a plugin by using the
appIconsconfiguration as shown in the following example:# dynamic-plugins-config.yaml plugins: - plugin: <plugin_path_or_url> disabled: false pluginConfig: dynamicPlugins: frontend: my-plugin: # The plugin package name appIcons: - name: <catalogName> # The icon catalog name # (Optional): The set of assets to access within the plugin. If not specified, the system uses the PluginRoot module. module: CustomModule # (Optional): The actual component name to be rendered as a standalone page. If not specified, the system uses the default export. importName: <CustomIcon>NoteThe
package_namekey underdynamicPlugins.frontendmust match thescalprum.namevalue in your plugin’spackage.json. This ensures your dynamic plugin loads correctly during runtime.
8.3. Define dynamic routes for new plugin pages Copy linkLink copied to clipboard!
Use this procedure to configure dynamic routes for new plugin pages in the application.
Procedure
-
Define each route by specifying a unique
pathand, if needed, animportNameif it is different from thedefaultexport. Expose additional routes in a dynamic plugin by configuring
dynamicRoutesas shown in the following example:# dynamic-plugins-config.yaml plugins: - plugin: <plugin_path_or_url> disabled: false pluginConfig: dynamicPlugins: frontend: my-plugin: # The plugin package name dynamicRoutes: # The unique path in the application. The path cannot override existing routes except the / home route. - path: /my-plugin # (Optional): The set of assets to access within the plugin. If not specified, the system uses the PluginRoot module. module: CustomModule # (Optional): The component name as a standalone page. If not specified, the system uses the default export. importName: <my_plugin>PluginPage # Allows you to extend the main sidebar navigation and point to a new route. menuItem: icon: # home | group | category | extension | school | <my_icon> text: <my-plugin label> enabled: false config: # (Optional): Passes props to a custom sidebar item props: ...The
menuItemaccepts the following properties:-
text: The label shown to the user. -
icon: The Backstage system icon name. -
enabled: Optional: When you set this to false, you can remove a menuItem from the sidebar. importName: Specifies the optional name of an exportedSidebarItemcomponent.To configure a custom
SidebarItemto enhance experiences such as notification badges, ensure the component accepts the following properties:export type MySidebarItemProps = { to: string; // supplied by the sidebar during rendering, this will be the path configured for the dynamicRoute };# dynamic-plugins-config.yaml plugins: - plugin: <plugin_path_or_url> disabled: false pluginConfig: dynamicPlugins: frontend: my-dynamic-plugin-package-name: dynamicRoutes: - importName: CustomPage menuItem: config: props: text: Click Me! importName: SimpleSidebarItem path: /custom_page
8.5. Binding to existing plugins Copy linkLink copied to clipboard!
You can bind to existing plugins and their routes, and declare new targets sourced from dynamic plugins as shown in the following routeBindings configuration:
# dynamic-plugins-config.yaml
plugins:
- plugin: <plugin_path_or_url>
disabled: false
pluginConfig:
dynamicPlugins:
frontend:
my-plugin:
routeBindings:
targets:
- name: <plugin_name>Plugin
importName: <plugin_key>Plugin
module: CustomModule
bindings:
- bindTarget: "<plugin_name>Plugin.externalRoutes"
bindMap:
headerLink: "<plugin_name>Plugin.routes.root"
where:
my-plugin- The plugin package name.
targets- A new bind target.
name-
Optional: Defaults to
importName. Explicit name of the plugin that exposes the bind target. importName-
Required: Explicit import name that references a
BackstagePlugin<{}>implementation. module-
Optional: Same as the key in
scalprum.exposedModulesin thepackage.jsonfile of the plugin. bindTarget- Required: One of the supported or imported bind targets.
bindMap-
Required: A map of route bindings similar to
bindfunction options.
To configure routeBindings, complete the following steps:
-
Define new targets using
routeBindings.targets. Set the requiredimportNameto aBackstagePlugin<{}>implementation. Declare route bindings using the
routeBindings.bindingsfield by settingbindTargetto the name of the target to bind to. This is a dynamic or static target, such as:-
catalogPlugin.externalRoutes -
catalogImportPlugin.externalRoutes -
techdocsPlugin.externalRoutes scaffolderPlugin.externalRoutesYou can extend existing pages with additional content by using mount points. The application defines these identifiers throughout the system.
-
8.6. Using mount points Copy linkLink copied to clipboard!
Mount points let you attach UI components to predefined locations in the Red Hat Developer Hub interface, such as entity pages, application headers, listeners, and providers.
8.6.1. Using mount points Copy linkLink copied to clipboard!
Red Hat Developer Hub defines mount points as identifiers available across the application. You can use these points to extend existing pages with additional content.
8.6.1.1. Customizing entity page Copy linkLink copied to clipboard!
You can extend catalog components and additional views.
The available mount points include the following:
| Mount point | Description | Visible even when you enable no plugins |
|---|---|---|
|
| Administration plugins page | NO |
|
| Administration RBAC page | NO |
|
| Catalog entity menu | YES for all entities |
|
| Catalog entity overview page | YES for all entities |
|
| Catalog entity Topology tab | NO |
|
| Catalog entity Issues tab | NO |
|
| Catalog entity Pull Requests tab | NO |
|
| Catalog entity CI tab | NO |
|
| Catalog entity CD tab | NO |
|
| Catalog entity Kubernetes tab | NO |
|
| Catalog entity Image Registry tab | NO |
|
| Catalog entity Monitoring tab | NO |
|
| Catalog entity Lighthouse tab | NO |
|
| Catalog entity API tab |
YES for entity of |
|
| Catalog entity Dependencies tab |
YES for entity of |
|
| Catalog entity Documentation tab |
YES for entity that satisfies |
|
| Catalog entity Definitions tab |
YES for entity of |
|
| Catalog entity Diagram tab |
YES for entity of |
|
| Search result type | YES, default catalog search type is available |
|
| Search filters | YES, default catalog kind and lifecycle filters are visible |
|
| Search results content | YES, default catalog search is present |
Mount points within a catalog such as entity.page. render as tabs and become visible only if at least one plugin contributes to them, or if they can render static content.
Each entity.page. mount point has the following variations:
-
/contexttype that serves to create React contexts -
/cardstype for regular React components
The following is an example of the overall configuration structure of a mount point:
# dynamic-plugins-config.yaml
plugins:
- plugin: <plugin_path_or_url>
disabled: false
pluginConfig:
dynamicPlugins:
frontend:
my-plugin: # The plugin package name
mountPoints: # (Optional): Uses existing mount points
- mountPoint: <mountPointName>/[cards|context]
module: CustomModule
importName: <pluginName>PluginPage
config: # (Optional): Allows you to pass additional configuration to the component
layout: {} # Used only in /cards type which renders visible content
# Use only in /cards type which renders visible content. if is passed to <EntitySwitch.Case if={<here>}.
if:
allOf|anyOf|oneOf:
- isMyPluginAvailable
- isKind: component
- isType: service
- hasAnnotation: annotationKey
props: {} # Useful when you are passing additional data to the component
The available conditions include:
-
allOf: The configuration must meet all conditions -
anyOf: The configuration must meet at least one condition -
oneOf: The configuration must meet only one condition
Conditions are:
-
isKind: Accepts a string or a list of string with entity kinds. For exampleisKind: componentrenders the component only for entity ofkind: Component. -
isType: Accepts a string or a list of string with entity types. For exampleisType: servicerenders the component only for entities ofspec.type: 'service'. -
hasAnnotation: Accepts a string or a list of string with annotation keys. For examplehasAnnotation: my-annotationrenders the component only for entities that have definedmetadata.annotations['my-annotation']. -
Condition imported from the
moduleof the plugin: Must be function name exported from the samemodulewithin the plugin. For exampleisMyPluginAvailablerenders the component only ifisMyPluginAvailablefunction returnstrue. The function must have the following signature:(e: Entity) ⇒ boolean.
The entity page supports adding more items to the menu at the top right of the page. The exported component is a form of dialog wrapper component that accepts an open boolean property and an onClose event handler property as shown in the following example:
export type SimpleDialogProps = {
open: boolean;
onClose: () => void;
};
You can configure the menu entry by using the props configuration entry for the mount point. The title and icon properties sets the text and icon of the menu item. You can use any system icon or icon added through a dynamic plugin. The following is an example configuration:
# dynamic-plugins-config.yaml
plugins:
- plugin: <plugin_path_or_url>
disabled: false
pluginConfig:
dynamicPlugins:
frontend:
my-dynamic-plugin-package:
appIcons:
- name: dialogIcon
importName: DialogIcon
mountPoints:
- mountPoint: entity.context.menu
importName: SimpleDialog
config:
props:
title: Open Simple Dialog
icon: dialogIcon
8.6.1.2. Adding application header Copy linkLink copied to clipboard!
You can customize global headers by specifying configurations in the app-config.yaml file as shown in the following example:
# app-config.yaml
dynamicPlugins:
frontend:
my-plugin: # The plugin package name
mountPoints:
- mountPoint: application/header # Adds the header as a global header
importName: <header_component> # Specifies the component exported by the global header plugin
config:
position: above-main-content # Supported values: (`above-main-content`| above-sidebar`)
To configure many global headers at different positions, add entries to the mountPoints field.
8.6.1.3. Adding application listeners Copy linkLink copied to clipboard!
You can add application listeners by using the application/listener mount point as shown in the following example:
# app-config.yaml
dynamicPlugins:
frontend:
my-plugin: # The plugin package name
mountPoints:
- mountPoint: application/listener
importName: <exported listener component>
You can configure many application listeners by adding entries to the mountPoints field.
8.6.1.4. Adding application providers Copy linkLink copied to clipboard!
You can add application providers by using the application/provider mount point. You can use a mount point to configure a context provider as shown in the following example:
# app-config.yaml
dynamicPlugins:
frontend:
my-plugin: # The plugin package name
dynamicRoutes:
- path: /<route>
importName: Component # The component to load on the route
mountPoints:
- mountPoint: application/provider
importName: <exported provider component>
-
You can configure many application providers by adding entries to the
mountPointsfield. -
The
package_namekey underdynamicPlugins.frontendmust match thescalprum.namevalue in thepackage.jsonfile of your plugin. This ensures your dynamic plugin loads correctly at runtime.
8.7. Customizing and extending entity tabs Copy linkLink copied to clipboard!
You can customize and extend the set of tabs by using the entityTabs configuration as follows:
# dynamic-plugins-config.yaml
plugins:
- plugin: <plugin_path_or_url>
disabled: false
pluginConfig:
dynamicPlugins:
frontend:
my-plugin: # The plugin package name
entityTabs:
# Specify a new tab
- path: /new-path
title: My New Tab
mountPoint: entity.page.my-new-tab
# Change an existing tab's title or mount point
- path: /
title: General
mountPoint: entity.page.overview
# Specify the sub-path route in the catalog where this tab is available
- path: "/pr"
title: "Changed Pull/Merge Requests" # Specify the title you want to display
priority: 1
# The base mount point name available on the tab. This name expands to create two mount points per tab, with` /context` and with `/cards`
mountPoint: "entity.page.pull-requests"
- path: "/"
title: "Changed Overview"
mountPoint: "entity.page.overview"
# Specify the order of tabs. The tabs with higher priority values appear first
priority: -6
You can configure dynamic front-end plugins to target the mount points exposed by the entityTabs configuration. The following are the default catalog entity routes in the default order:
| Route | Title | Mount Point | Entity Kind |
|---|---|---|---|
|
| Overview |
| Any |
|
| Topology |
| Any |
|
| Issues |
| Any |
|
| CPull/Merge Requests |
| Any |
|
| CI |
| VAny |
|
| CD |
| Any |
|
| Kubernetes |
| Any |
|
| Image Registry |
| Any |
|
| Monitoring |
| Any |
|
| Lighthouse |
| Any |
|
| Api |
| kind: Service or kind: Component |
|
| Dependencies |
| kind: Component |
|
| Docs |
| Any |
|
| Definition |
| kind: API |
|
| Diagram |
| kind: System |
Mount points within Catalog such as `entity.page.*` render as tabs and become visible only if at least one plugin contributes to them, or if they can render static content.
8.8. Using a custom SignInPage component Copy linkLink copied to clipboard!
In Red Hat Developer Hub (RHDH), the SignInPage component manages authentication flow. By default, Developer Hub uses a static SignInPage.
When you configure a custom SignInPage:
-
The system loads the specified
importNamecomponent from your dynamic plugin. -
The component returns a configured
SignInPagethat connects the required authentication provider factories. -
You specify only one
signInPagefor the application at a time.
dynamicPlugins:
frontend:
my-plugin: # The plugin package name
signInPage:
importName: CustomSignInPage
The package_name specified under dynamicPlugins.frontend must match the scalprum.name value in the package.json file of your plugin to ensure the dynamic plugin loads correctly at runtime.
The module field is optional and specifies the set of assets that you must access within the dynamic plugin. By default, the system uses the PluginRoot module.
8.9. Providing custom Scaffolder field extensions Copy linkLink copied to clipboard!
With the Scaffolder component in Red Hat Developer Hub (RHDH), you can create software components by using templates through a guided wizard. You can extend the functionality of the Scaffolder by adding custom form fields as dynamic plugins by using the scaffolderFieldExtensions configuration.
With custom field extensions, you can add specialized form fields that capture domain-specific data during the scaffolding process, such as environment selectors, input validations, or repository checks.
When you configure custom Scaffolder field extensions:
-
The dynamic plugin exposes the field extension component using
createScaffolderFieldExtension. -
Each field extension requires a unique
importNamefor registration. - You register many field extensions by listing each in the configuration.
dynamicPlugins:
frontend:
my-plugin: # The plugin package name
scaffolderFieldExtensions:
- importName: MyNewFieldExtension # References the exported Scaffolder field extension component from your plugin
The module field is optional and specifies which set of assets to access within the plugin. By default, the system uses the PluginRoot module, consistent with the scalprum.exposedModules key in the package.json file of your package.
8.10. Providing additional utility APIs Copy linkLink copied to clipboard!
If a dynamic plugin exports the plugin object returned by createPlugin, the createApp API receives it. All API factories exported by the plugin are automatically registered and available in the front-end application.
You can add an entry to the dynamicPlugins.frontend configuration when a dynamic plugin has only API factories as shown in the following example:
# app-config.yaml
dynamicPlugins:
frontend:
my-dynamic-plugin-package-with-api-factories: {}
However, when the dynamic plugin is not exporting the plugin object, you must explicitly configure each API factory. Use the apiFactories configuration to register them with the createApp API as shown in the following example:
# app-config.yaml
dynamicPlugins:
frontend:
my-plugin: # The plugin package name
apiFactories:
# (Optional): Specify the import name that references a `AnyApiFactory<{}>` implementation. (Defaults to `default` export)
- importName: BarApi
# (Optional): An argument which specifies the assets you want to access within the plugin. If not provided, the default module named `PluginRoot` is used
module: CustomModule
An API factory from a dynamic plugin overrides the API factories that the Developer Hub application initializes when both specify the same API ref ID. A dynamic plugin can export AnyApiFactory<{}> to cater for some specific use case as shown in the following example:
export const customScmAuthApiFactory = createApiFactory({
api: scmAuthApiRef,
deps: { githubAuthApi: githubAuthApiRef },
factory: ({ githubAuthApi }) =>
ScmAuth.merge(
ScmAuth.forGithub(githubAuthApi, { host: "github.someinstance.com" }),
ScmAuth.forGithub(githubAuthApi, {
host: "github.someotherinstance.com",
}),
),
});
The corresponding configuration that overrides the default ScmAuth API factory that Developer Hub defaults to is as shown in the following example:
dynamicPlugins:
frontend:
my-plugin: # The plugin package name
apiFactories:
- importName: customScmAuthApiFactory
8.11. Adding custom authentication provider settings Copy linkLink copied to clipboard!
You can install new authentication providers from a dynamic plugin that either adds additional configuration support for an existing provider or adds a new authentication provider. The user settings section lists these providers under the Authentication Providers tab.
You can use the providerSettings configuration to add entries for an authentication provider from a dynamic plugin, as shown in the following example:
dynamicPlugins:
frontend:
my-plugin: # The plugin package name
providerSettings:
# The title for the authentication provider shown above the user's profile image if available
- title: My Custom Auth Provider
# The description of the authentication provider
description: Sign in using My Custom Auth Provider
# The ID of the authentication provider as provided to the `createApiRef` API call.
provider: core.auth.my-custom-auth-provider
provider looks up the corresponding API factory for the authentication provider to connect the provider’s Sign In/Sign Out button.
8.12. Providing custom TechDocs add-ons Copy linkLink copied to clipboard!
If a plugin provides many add-ons, each techdocsAddon entry specifies a unique importName corresponding to the add-on. Front-end plugins expose the TechDocs add-on component by using the techdocsAddons configuration as shown in the following example:
dynamicPlugins:
frontend:
my-plugin: # The plugin package name
techdocsAddons:
- importName: ExampleAddon # The exported add-on component
config:
props: ... # (Optional): React props to pass to the add-on
8.13. Customizing Red Hat Developer Hub theme Copy linkLink copied to clipboard!
You can customize Developer Hub themes from a dynamic plugin with various configurations as shown in the following example:
import { lightTheme } from './lightTheme';
import { darkTheme } from './darkTheme';
import { UnifiedThemeProvider } from '@backstage/theme';
export const lightThemeProvider = ({ children }: { children: ReactNode }) => (
<UnifiedThemeProvider theme={lightTheme} children={children} />
);
export const darkThemeProvider = ({ children }: { children: ReactNode }) => (
<UnifiedThemeProvider theme={darkTheme} children={children} />
);
For more information about creating a custom theme, see creating a custom theme.
You can declare the theme by using the themes configuration as shown in the following example:
dynamicPlugins:
frontend:
my-plugin: # The plugin package name
themes:
# are `light` or `dark`. Using 'light' overrides the app-provided light theme
- id: light
title: Light
variant: light
icon: someIconReference
importName: lightThemeProvider
# The theme name displayed to the user on the *Settings* page. Using 'dark' overrides the app-provided dark theme
- id: dark
title: Dark
variant: dark
icon: someIconReference # A string reference to a system or app icon
# The name of the exported theme provider function, the function signature should match `({ children }: { children: ReactNode }): React.JSX.Element`
importName: darkThemeProvider