Chapter 3. Custom plugins in Red Hat Developer Hub
You can integrate custom dynamic plugins into Red Hat Developer Hub to enhance its functionality without modifying its source code or rebuilding it. To add these plugins, export them as derived packages.
While exporting the plugin package, you must ensure that dependencies are correctly bundled or marked as shared, depending on their relationship to the Developer Hub environment.
To integrate a custom plugin into Developer Hub:
- First, obtain the plugin’s source code.
- Export the plugin as a dynamic plugin package. See Section 3.1, “Exporting custom plugins in Red Hat Developer Hub”.
- Package and publish the dynamic plugin. See Section 3.2, “Packaging and publishing custom plugins as dynamic plugins”.
- Install the plugin in the Developer Hub environment. See Section 3.3, “Installing custom plugins in Red Hat Developer Hub”.
3.1. Exporting custom plugins in Red Hat Developer Hub Copy linkLink copied to clipboard!
To use plugins in Red Hat Developer Hub, you can export plugins as derived dynamic plugin packages. These packages contain the plugin code and dependencies, ready for dynamic plugin integration into Developer Hub.
Prerequisites
The
@red-hat-developer-hub/clipackage is installed. Use the latest version (@latesttag) for compatibility with the most recent features and fixes.NoteUse the
npx @red-hat-developer-hub/cli@latest plugin exportcommand to export an existing custom plugin as a derived dynamic plugin package.You must use this command when you have the source code for a custom plugin and want to integrate it into RHDH as a dynamic plugin.
The command processes the plugin’s source code and dependencies and generates the necessary output for dynamic loading by RHDH.
For an example of using this command, see Example of installing a custom plugin in Red Hat Developer Hub.
- Node.js and NPM is installed and configured.
- The custom plugin is compatible with your Red Hat Developer Hub version. For more information, see Version compatibility matrix.
The custom plugin must have a valid
package.jsonfile in its root directory, containing all required metadata and dependencies.- Backend plugins
To ensure compatibility with the dynamic plugin support and enable their use as dynamic plugins, existing backend plugins must be compatible with the new Backstage backend system. Additionally, these plugins must be rebuilt using a dedicated CLI command.
The new Backstage backend system entry point (created using
createBackendPlugin()orcreateBackendModule()) must be exported as the default export from either the main package or analphapackage (if the plugin instance support is still provided usingalphaAPIs). This doesn’t add any additional requirement on top of the standard plugin development guidelines of the plugin instance.The dynamic export mechanism identifies private dependencies and sets the
bundleDependenciesfield in thepackage.jsonfile. This export mechanism ensures that the dynamic plugin package is published as a self-contained package, with its private dependencies bundled in a privatenode_modulesfolder.Certain plugin dependencies require specific handling in the derived packages, such as:
Shared dependencies are provided by the RHDH application and listed as
peerDependenciesinpackage.jsonfile, not bundled in the dynamic plugin package. For example, by default, all@backstagescoped packages are shared.You can use the
--shared-packageflag to specify shared dependencies, that are expected to be provided by Red Hat Developer Hub application and not bundled in the dynamic plugin package.To treat a
@backstagepackage as private, use the negation prefix (!). For example, when a plugin depends on the package in@backstagethat is not provided by the Red Hat Developer Hub application.Embedded dependencies are bundled into the dynamic plugin package with their dependencies hoisted to the top level. By default, packages with
-nodeor-commonsuffixes are embedded.You can use the
--embed-packageflag to specify additional embedded packages. For example, packages from the same workspace that do not follow the default naming convention.The following is an example of exporting a dynamic plugin with shared and embedded packages:
Example dynamic plugin export with shared and embedded packages
$ npx @red-hat-developer-hub/cli@latest plugin export --shared-package '!/@backstage/plugin-notifications/' --embed-package @backstage/plugin-notifications-backendIn the previous example:
-
@backstage/plugin-notificationspackage is treated as a private dependency and is bundled in the dynamic plugin package, despite being in the@backstagescope. -
@backstage/plugin-notifications-backendpackage is marked as an embedded dependency and is bundled in the dynamic plugin package.
- Front-end plugins
Front-end plugins can use
scalprumfor configuration, which the CLI can generate automatically during the export process. The generated default configuration is logged when running the following command:Example command to log the default configuration
$ npx @red-hat-developer-hub/cli@latest plugin exportThe following is an example of default
scalprumconfiguration:Default
scalprumconfiguration"scalprum": { "name": "<package_name>", // The Webpack container name matches the NPM package name, with "@" replaced by "." and "/" removed. "exposedModules": { "PluginRoot": "./src/index.ts" // The default module name is "PluginRoot" and doesn't need explicit specification in the app-config.yaml file. } }You can add a
scalprumsection to thepackage.jsonfile. For example:Example
scalprumcustomization"scalprum": { "name": "custom-package-name", "exposedModules": { "FooModuleName": "./src/foo.ts", "BarModuleName": "./src/bar.ts" // Define multiple modules here, with each exposed as a separate entry point in the Webpack container. } }Dynamic plugins might need adjustments for Developer Hub needs, such as static JSX for mountpoints or dynamic routes. These changes are optional but might be incompatible with static plugins.
To include static JSX, define an additional export and use it as the dynamic plugin’s
importName. For example:Example static and dynamic plugin export
// For a static plugin $ export const EntityTechdocsContent = () => {...} // For a dynamic plugin $ export const DynamicEntityTechdocsContent = { element: EntityTechdocsContent, staticJSXContent: ( <TechDocsAddons> <ReportIssue /> </TechDocsAddons> ), };
Procedure
Use the
plugin exportcommand from the@red-hat-developer-hub/clipackage to export the plugin:Example command to export a custom plugin
$ npx @red-hat-developer-hub/cli@latest plugin exportEnsure that you execute the previous command in the root directory of the plugin’s JavaScript package (containing
package.jsonfile).The resulting derived package will be located in the
dist-dynamicsubfolder. The exported package name consists of the original plugin name with-dynamicappended.WarningThe derived dynamic plugin JavaScript packages must not be published to the public NPM registry. For more appropriate packaging options, see Section 3.2, “Packaging and publishing custom plugins as dynamic plugins”. If you must publish to the NPM registry, use a private registry.
3.2. Packaging and publishing custom plugins as dynamic plugins Copy linkLink copied to clipboard!
After exporting a custom plugin, you can package the derived package into one of the following supported formats:
- Open Container Initiative (OCI) image (recommended)
- TGZ file
JavaScript package
ImportantExported dynamic plugin packages must only be published to private NPM registries.
3.2.1. Creating an OCI image with dynamic packages Copy linkLink copied to clipboard!
Prerequisites
-
You have installed
podmanordocker. - You have exported a custom dynamic plugin package. For more information, see Section 3.1, “Exporting custom plugins in Red Hat Developer Hub”.
Procedure
-
Navigate to the plugin’s root directory (not the
dist-dynamicdirectory). Run the following command to package the plugin into an OCI image:
Example command to package an exported custom plugin
$ npx @red-hat-developer-hub/cli@latest plugin package --tag quay.io/example/image:v0.0.1In the previous command, the
--tagargument specifies the image name and tag.Run one of the following commands to push the image to a registry:
Example command to push an image to a registry using podman
$ podman push quay.io/example/image:v0.0.1Example command to push an image to a registry using docker
$ docker push quay.io/example/image:v0.0.1The output of the
package-dynamic-pluginscommand provides the plugin’s path for use in thedynamic-plugin-config.yamlfile.
3.2.2. Creating a TGZ file with dynamic packages Copy linkLink copied to clipboard!
Prerequisites
- You have exported a custom dynamic plugin package. For more information, see Section 3.1, “Exporting custom plugins in Red Hat Developer Hub”.
Procedure
-
Navigate to the
dist-dynamicdirectory. Run the following command to create a
tgzarchive:Example command to create a
tgzarchive$ npm packYou can obtain the integrity hash from the output of the
npm packcommand by using the--jsonflag as follows:Example command to obtain the integrity hash of a
tgzarchive$ npm pack --json | head -n 10Host the archive on a web server accessible to your RHDH instance, and reference its URL in the
dynamic-plugin-config.yamlfile as follows:Example
dynamic-plugin-config.yamlfileplugins: - package: https://example.com/backstage-plugin-myplugin-1.0.0.tgz integrity: sha512-<hash>Run the following command to package the plugins:
Example command to package a dynamic plugin
$ npm pack --pack-destination ~/test/dynamic-plugins-root/TipTo create a plugin registry using HTTP server on OpenShift Container Platform, run the following commands:
Example commands to build and deploy an HTTP server in OpenShift Container Platform
$ oc project my-rhdh-project $ oc new-build httpd --name=plugin-registry --binary $ oc start-build plugin-registry --from-dir=dynamic-plugins-root --wait $ oc new-app --image-stream=plugin-registryConfigure your RHDH to use plugins from the HTTP server by editing the
dynamic-plugin-config.yamlfile:Example configuration to use packaged plugins in RHDH
plugins: - package: http://plugin-registry:8080/backstage-plugin-myplugin-1.9.6.tgz
3.2.3. Creating a JavaScript package with dynamic packages Copy linkLink copied to clipboard!
The derived dynamic plugin JavaScript packages must not be published to the public NPM registry. If you must publish to the NPM registry, use a private registry.
Prerequisites
- You have exported a custom dynamic plugin package. For more information, see Section 3.1, “Exporting custom plugins in Red Hat Developer Hub”.
Procedure
-
Navigate to the
dist-dynamicdirectory. Run the following command to publish the package to your private NPM registry:
Example command to publish a plugin package to an NPM registry
$ npm publish --registry <npm_registry_url>TipYou can add the following to your
package.jsonfile before running theexportcommand:Example
package.jsonfile{ "publishConfig": { "registry": "<npm_registry_url>" } }If you modify
publishConfigafter exporting the dynamic plugin, re-run theplugin exportcommand to ensure the correct configuration is included.
3.3. Installing custom plugins in Red Hat Developer Hub Copy linkLink copied to clipboard!
You can install a custom plugins in Red Hat Developer Hub without rebuilding the RHDH application.
The location of the dynamic-plugin-config.yaml file depends on the deployment method. For more details, refer to Installing dynamic plugins with the Red Hat Developer Hub Operator and Installing dynamic plugins using the Helm chart.
Plugins are defined in the plugins array within the dynamic-plugin-config.yaml file. Each plugin is represented as an object with the following properties:
-
package: The plugin’s package definition, which can be an OCI image, a TGZ file, a JavaScript package, or a directory path. -
disabled: A boolean value indicating whether the plugin is enabled or disabled. -
integrity: The integrity hash of the package, required for TGZ file and JavaScript packages. -
pluginConfig: The plugin’s configuration. For backend plugins, this is optional; for frontend plugins, it is required. ThepluginConfigis a fragment of theapp-config.yamlfile, and any added properties are merged with the RHDHapp-config.yamlfile.
You can also load dynamic plugins from another directory, though this is intended for development or testing purposes and is not recommended for production, except for plugins included in the RHDH container image. For more information, see Chapter 5, Enabling plugins added in the RHDH container image.
3.3.1. Loading a plugin packaged as an OCI image Copy linkLink copied to clipboard!
Prerequisites
The custom plugin is packaged as a dynamic plugin in an OCI image.
For more information about packaging a custom plugin, see Section 3.2, “Packaging and publishing custom plugins as dynamic plugins”.
Procedure
To retrieve plugins from an authenticated registry, complete the following steps:
Log in to the container image registry.
podman login <registry>Verify the content of the
auth.jsonfile created after the login.cat ${XDG_RUNTIME_DIR:-~/.config}/containers/auth.jsonCreate a secret file using the following example:
oc create secret generic _<secret_name>_ --from-file=auth.json=${XDG_RUNTIME_DIR:-~/.config}/containers/auth.json1 -
For an Operator-based deployment, replace <secret_name> with
dynamic-plugins-registry-auth. -
For a Helm-based deployment, replace <secret_name> with
<Helm_release_name>_-dynamic-plugins-registry-auth.
-
For an Operator-based deployment, replace <secret_name> with
Define the plugin with the
oci://prefix in the following format indynamic-plugins.yamlfile:oci://<image_name>:<tag>!<plugin_name>Example configuration in
dynamic-plugins.yamlfileplugins: - disabled: false package: oci://quay.io/example/image:v0.0.1!backstage-plugin-mypluginTo perform an integrity check, use the image digest in place of the tag in the
dynamic-plugins.yamlfile as shown in the following example:Example configuration in
dynamic-plugins.yamlfileplugins: - disabled: false package: oci://quay.io/example/image@sha256:28036abec4dffc714394e4ee433f16a59493db8017795049c831be41c02eb5dc!backstage-plugin-myplugin- To apply the changes, restart the RHDH application.
3.3.2. Loading a plugin packaged as a TGZ file Copy linkLink copied to clipboard!
Prerequisites
The custom plugin is packaged as a dynamic plugin in a TGZ file.
For more information about packaging a custom plugin, see Section 3.2, “Packaging and publishing custom plugins as dynamic plugins”.
Procedure
Specify the archive URL and its integrity hash in the
dynamic-plugins.yamlfile using the following example:Example configuration in
dynamic-plugins.yamlfileplugins: - disabled: false package: https://example.com/backstage-plugin-myplugin-1.0.0.tgz integrity: sha512-9WlbgEdadJNeQxdn1973r5E4kNFvnT9GjLD627GWgrhCaxjCmxqdNW08cj+Bf47mwAtZMt1Ttyo+ZhDRDj9PoA==- To apply the changes, restart the RHDH application.
3.3.3. Loading a plugin packaged as a JavaScript package Copy linkLink copied to clipboard!
Prerequisites
The custom plugin is packaged as a dynamic plugin in a JavaScript package.
For more information about packaging a custom plugin, see Section 3.2, “Packaging and publishing custom plugins as dynamic plugins”.
Procedure
Run the following command to obtain the integrity hash from the NPM registry:
$ npm view --registry <registry-link> <npm package>@<version> dist.integritySpecify the package name, version, and its integrity hash in the
dynamic-plugins.yamlfile as follows:Example configuration in
dynamic-plugins.yamlfileplugins: - disabled: false package: @example/backstage-plugin-myplugin@1.0.0 integrity: sha512-9WlbgEdadJNeQxdn1973r5E4kNFvnT9GjLD627GWgrhCaxjCmxqdNW08cj+Bf47mwAtZMt1Ttyo+ZhDRDj9PoA==If you are using a custom NPM registry, create a
.npmrcfile with the registry URL and authentication details:Example code for
.npmrcfileregistry=<registry-link> //<registry-link>:_authToken=<auth-token>When using OpenShift Container Platform or Kubernetes:
Use the Helm chart to add the
.npmrcfile by creating a secret. For example:Example secret configuration
apiVersion: v1 kind: Secret metadata: name: <release_name>-dynamic-plugins-npmrc1 type: Opaque stringData: .npmrc: | registry=<registry-link> //<registry-link>:_authToken=<auth-token>For RHDH Helm chart, name the secret using the following format for automatic mounting:
<release_name>-dynamic-plugins-npmrc
- To apply the changes, restart the RHDH application.
3.4. Example of installing a custom plugin in Red Hat Developer Hub Copy linkLink copied to clipboard!
This example demonstrates how to package and install dynamic plugins using the Backstage Entity Feedback community plugin that is not included in Red Hat Developer Hub pre-installed dynamic plugins.
Limitations
You need to ensure that your custom plugin is built with a compatible version of Backstage. In Developer Hub, click Settings. Your custom plugin must be compatible with the Backstage Version (or the closest previous version) that is displayed in the Metadata section of Red Hat Developer Hub.
For example, if you view the history of the
backstage.jsonfile for the Entity Feedback plugin, the1fc87decommit is closest previous version to Backstage version of 1.39.1.Figure 3.1.
backstage.jsonfile history in Github
Prerequisites
- Your local environment meets the following requirements:
- Node.js: Version 22.x
- Yarn: Version 4.x
- git CLI
- jq CLI: Command-line JSON processor
- OpenShift CLI (oc): The client for interacting with your OpenShift cluster.
- Container runtime: Either podman or docker is required for packaging the plugin into an OCI image and logging into registries.
- Container registry access: Access to an OCI-compliant container registry (such as the internal OpenShift registry or a public registry like Quay.io).
Procedure
Clone the source code for the Entity Feedback plugin, as follows:
$ git clone https://github.com/backstage/community-plugins.git $ cd community-pluginsPrepare your environment to build the plugin by enabling Yarn for your Node.js installation, as follows:
$ corepack enable yarnInstall the dependencies, compile the code, and build the plugins, as follows:
$ cd workspaces/entity-feedback $ yarn install $ yarn tsc $ yarn build:allNoteAfter this step, with upstream Backstage, you publish the built plugins to a NPM or NPM-compatible registry. In this example, as you are building this plugin to support it being loaded dynamically by Red Hat Developer Hub, you can skip the
npm publishstep that publishes the plugin to a NPM registry. Instead, you can package the plugin for dynamic loading and publish it as a container image onQuay.ioor your preferred container registry.Prepare the Entity Feedback frontend plugin by using the Red Hat Developer Hub CLI. The following command uses the plugin files in the
distfolder that was generated by theyarn build:allcommand, and creates a newdist-scalprumfolder that contains the necessary configuration and source files to enable dynamic loading:$ cd plugins/entity-feedback $ npx @red-hat-developer-hub/cli@latest plugin exportWhen this command packages a frontend plugin, it uses a default Scalprum configuration if one is not found. The Scalprum configuration is used to specify the plugin entry point and exports, and then to build a
dist-scalprumfolder that contains the dynamic plugin. The default Scalprum configuration is shown below, however ascalprumkey can be added to thepackage.jsonfile used by your plugin to set custom values, if necessary:{ "name": "backstage-community.plugin-entity-feedback", "exposedModules": { "PluginRoot": "./src/index.ts" } }The following
plugin-manifest.jsonfile, which Red Hat Developer Hub uses to load the plugin, is located in thedist-dynamic/dist-scalprumfolder:{ "name": "backstage-community.plugin-entity-feedback", "version": "0.6.0", "extensions": [], "registrationMethod": "callback", "baseURL": "auto", "loadScripts": [ "backstage-community.plugin-entity-feedback.fd691533c03cb52c30ac.js" ], "buildHash": "fd691533c03cb52c30acbb5a80197c9d" }Package the plugin into a container image and publish it to Quay.io or your preferred container registry:
$ export QUAY_USER=replace-with-your-username $ export PLUGIN_NAME=entity-feedback-plugin $ export VERSION=$(cat package.json | jq .version -r) $ npx @red-hat-developer-hub/cli@latest plugin package \ --tag quay.io/$QUAY_USER/$PLUGIN_NAME:$VERSION $ podman login quay.io $ podman push quay.io/$QUAY_USER/$PLUGIN_NAME:$VERSIONRepeat the same steps for the backend plugin. Scalprum is not required for backend plugins, and a
dist-dynamicfolder is generated instead of adist-scalprumfolder:$ cd ../entity-feedback-backend/ $ npx @red-hat-developer-hub/cli@latest plugin export $ export QUAY_USER=replace-with-your-username $ export PLUGIN_NAME=entity-feedback-plugin-backend $ export VERSION=$(cat package.json | jq .version -r) $ npx @red-hat-developer-hub/cli@latest plugin package \ --tag quay.io/$QUAY_USER/$PLUGIN_NAME:$VERSION $ podman push quay.io/$QUAY_USER/$PLUGIN_NAME:$VERSIONThose commands result in two container images being published to your container registry.
Figure 3.2. Container images published to Quay.io
3.4.1. Adding a custom dynamic plugin to Red Hat Developer Hub Copy linkLink copied to clipboard!
Procedure
To add your custom dynamic plugins to Red Hat Developer Hub. you must update the
dynamic-plugins.yamlfile by using the following configuration that is generated from thenpx @red-hat-developer-hub/cli@latest plugin packagecommand:plugins: - package: oci://quay.io/_<user_name>_/entity-feedback-plugin:0.5.0!backstage-community-plugin-entity-feedback disabled: false - package: oci://quay.io/_<user_name>_/entity-feedback-plugin-backend:0.6.0!backstage-community-plugin-entity-feedback-backend disabled: false
Ensure that your container images are publicly accessible, or that you have configured a pull secret in your environment. A pull secret provides Red Hat Developer Hub with credentials to authenticate pulling your plugin container images from a container registry.
3.4.2. Displaying the frontend plugin Copy linkLink copied to clipboard!
Procedure
You need to update the
pluginConfigsection of yourdynamic-plugins.yamlfile to specify how the Entity Feedback should be added to the Red Hat Developer Hub UI.dynamic-plugins.yamlfile fragment- package: oci://quay.io/_<user_name>_/entity-feedback-plugin:0.5.0!backstage-community-plugin-entity-feedback disabled: false pluginConfig: dynamicPlugins: frontend: backstage-community.plugin-entity-feedback: entityTabs: - mountPoint: entity.page.feedback path: /feedback title: Feedback mountPoints: - config: layout: gridColumn: 1 / -1 importName: StarredRatingButtons mountPoint: entity.page.feedback/cards - config: layout: gridColumn: 1 / -1 importName: EntityFeedbackResponseContent mountPoint: entity.page.feedback/cards - config: layout: gridColumnEnd: lg: span 6 md: span 6 xs: span 6 importName: StarredRatingButtons mountPoint: entity.page.overview/cardswhere:
backstage-community.plugin-entity-feedback:entityTabs-
Enter the
entityTabsarray to define a new tab, named “Feedback” on the Entity Overview screen in Red Hat Developer Hub. frontend:mountPointsThis array defines the following configurations to mount React components exposed by the plugin:
-
The
StarredRatingButtonscomponent is added to the new Feedback tab defined in entityTabs. -
Similar to the
StarredRatingButtons, theEntityFeedbackResponseContentis mounted on the Feedback tab. -
The
StarredRatingButtonsis added to the default Overview tab for each entity.
-
The
- To complete installing the Entity Feedback plugins, you must redeploy your Red Hat Developer Hub instance.
Verification
When your new instance of Red Hat Developer Hub has started, you can check that your plugins are installed and enabled by visiting the Administration > Extensions screen and searching for “entity” on the Installed tab.
When you click Catalog, you should see the new Feedback tab, and the StarredRatingButtons displayed, as follows:
Selecting a low star rating prompts the user to provide feedback, as follows:
The user provided feedback is not saved if you are logged in as the Guest user.