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.
- Package and publish the dynamic plugin.
- Install the plugin in the Developer Hub environment.
3.1. Export 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
You have installed the
@red-hat-developer-hub/clipackage. 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.
- You have installed and configured Node.js and NPM.
- 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.
You must export the new Backstage backend system entry point (created using
createBackendPlugin()orcreateBackendModule()) as the default export from either the main package or analphapackage. Export as analphapackage if the plugin instance support still usesalphaAPIs. This does not 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 you publish the dynamic plugin package 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: The RHDH application provides these dependencies and lists them as
peerDependenciesin thepackage.jsonfile. The dynamic plugin package does not bundle shared dependencies. For example, by default, all@backstagescoped packages use sharing.You can use the
--shared-packageflag to specify shared dependencies that Red Hat Developer Hub application provides and that the dynamic plugin package does not bundle.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: The dynamic plugin package bundles these dependencies with their dependencies hoisted to the top level. By default, the package embeds packages with
-nodeor-commonsuffixes.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:
$ npx @red-hat-developer-hub/cli@latest plugin export --shared-package '!/@backstage/plugin-notifications/' --embed-package @backstage/plugin-notifications-backendIn the earlier example:
-
The export treats the
@backstage/plugin-notificationspackage as a private dependency and bundles it in the dynamic plugin package, despite being in the@backstagescope. -
The export marks the
@backstage/plugin-notifications-backendpackage as an embedded dependency and bundles it in the dynamic plugin package.
- Front-end plugins
Front-end plugins can use
scalprumfor configuration. The CLI can generate this configuration automatically during the export process. When running the following command, the CLI logs the generated default configuration:$ npx @red-hat-developer-hub/cli@latest plugin exportThe following is an example of 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:"scalprum": { "name": "custom-package-name", "exposedModules": { "BazModuleName": "./src/baz.ts", "QuxModuleName": "./src/qux.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:// 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:$ npx @red-hat-developer-hub/cli@latest plugin exportEnsure that you run the earlier command in the root directory of the plugin’s JavaScript package (containing
package.jsonfile).The
dist-dynamicsubdirectory has the resulting derived package. The exported package name consists of the original plugin name with-dynamicappended.WarningDo not publish the derived dynamic plugin JavaScript packages to the public NPM registry. For more appropriate packaging options, see Section 3.2, “Package and publish custom plugins as dynamic plugins”. If you must publish to the NPM registry, use a private registry.
3.2. Package and publish 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
ImportantYou must publish exported dynamic plugin packages only to private NPM registries.
3.2.1. Create an OCI image with dynamic packages Copy linkLink copied to clipboard!
Use this procedure to package a dynamic plugin as an OCI image and push it to a container registry.
Prerequisites
-
You have installed
podmanordocker.
Procedure
-
Navigate to the plugin’s root directory (not the
dist-dynamicdirectory). Run the following command to package the plugin into an OCI image:
$ npx @red-hat-developer-hub/cli@latest plugin package --tag quay.io/example/image:v0.0.1In the earlier command, the
--tagargument specifies the image name and tag.Run one of the following commands to push the image to a registry:
$ podman push quay.io/example/image:v0.0.1$ 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. Create a TGZ file with dynamic packages Copy linkLink copied to clipboard!
Use this procedure to package a dynamic plugin as a TGZ file and host it on a web server.
Prerequisites
package-and-publish-plugins-as-dynamic-plugins
Procedure
-
Navigate to the
dist-dynamicdirectory. Run the following 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:$ 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:plugins: - package: https://example.com/backstage-plugin-myplugin-1.0.0.tgz integrity: sha512-<hash>Run the following command to package the plugins:
$ npm pack --pack-destination ~/test/dynamic-plugins-root/TipTo create a plugin registry using HTTP server on OpenShift Container Platform, run the following commands:
$ 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:plugins: - package: http://plugin-registry:8080/backstage-plugin-myplugin-1.9.6.tgz
3.2.3. Create a JavaScript package with dynamic packages Copy linkLink copied to clipboard!
Use this procedure to publish a dynamic plugin to a private NPM registry.
Do not publish the derived dynamic plugin JavaScript packages to the public NPM registry. If you must publish to the NPM registry, use a private registry.
Procedure
-
Navigate to the
dist-dynamicdirectory. Run the following command to publish the package to your private NPM registry:
$ npm publish --registry <npm_registry_url>TipYou can add the following to your
package.jsonfile before running theexportcommand:{ "publishConfig": { "registry": "<npm_registry_url>" } }If you change
publishConfigafter exporting the dynamic plugin, re-run theplugin exportcommand to ensure that the correct configuration is in the package.
3.3. Install 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.
The plugins array within the dynamic-plugin-config.yaml file defines plugins. Each object in the array represents a plugin 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 you enable or disable the plugin. -
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 front-end plugins, you must include it. ThepluginConfigis a fragment of theapp-config.yamlfile, and the system merges any added properties with the RHDHapp-config.yamlfile.
You can also load dynamic plugins from another directory, though development or testing purposes use this method and it is not recommended for production, except for plugins included in the RHDH container image.
3.3.1. Load a plugin packaged as an OCI image Copy linkLink copied to clipboard!
Use this procedure to load a dynamic plugin from an OCI image into Red Hat Developer Hub.
Prerequisites
You packaged the custom plugin as a dynamic plugin in an OCI image.
For more information about packaging a custom plugin, see Section 3.2, “Package and publish 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 by using one of the following formats in yourdynamic-plugins.yamlfile:- Standard definition
Use the format
oci://<image_name>:<tag>, as shown in the following example. The installation program automatically extracts the plugin path from the image metadata.Example configuration in
dynamic-plugins.yamlfile:plugins: - disabled: false package: oci://quay.io/example/image:v1.0.0NoteYou must package images with the
@red-hat-developer-hub/clito ensure the system applies theio.backstage.dynamic-packagesannotation.You must define exactly one plugin from that OCI image in the configuration files. The system returns an error if the configuration files contain many plugins or no matching plugins.
- Using image digests
To 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.yamlfile:plugins: - disabled: false package: oci://quay.io/example/image@sha256:28036abec4dffc714394e4ee433f16a59493db8017795049c831be41c02eb5dc- Using version inheritance
To inherit a version from a base configuration file, for example,
dynamic-plugins.default.yaml, use the{{inherit}}placeholder to inherit thev0.0.2tag, as shown in the following example:Example configuration in
dynamic-plugins.default.yamlfile:plugins: - disabled: false package: oci://quay.io/example/image:v0.0.2Example configuration in
dynamic-plugins.yamlfile:includes: - dynamic-plugins.default.yaml plugins: - disabled: false package: oci://quay.io/example/image:{{inherit}}NoteAn error occurs if you use
{{inherit}}in the includes file itself or if no matching plugin key exists in the base configuration.
- To apply the changes, restart the RHDH application.
3.3.2. Load a plugin packaged as a TGZ file Copy linkLink copied to clipboard!
Use this procedure to load a dynamic plugin from a TGZ file into Red Hat Developer Hub.
Prerequisites
You have packaged the custom plugin as a dynamic plugin in a TGZ file.
For more information about packaging a custom plugin, see Section 3.2, “Package and publish custom plugins as dynamic plugins”.
Procedure
Specify the archive URL and its integrity hash in the
dynamic-plugins.yamlfile using the following example:plugins: - 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. Load a plugin packaged as a JavaScript package Copy linkLink copied to clipboard!
Use this procedure to load a dynamic plugin from a JavaScript package into Red Hat Developer Hub.
Prerequisites
You have packaged the custom plugin as a dynamic plugin in a JavaScript package.
For more information about packaging a custom plugin, see Section 3.2, “Package and publish 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:plugins: - 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:registry=<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:apiVersion: v1 kind: Secret metadata: name: <release_name>-dynamic-plugins-npmrc type: Opaque stringData: .npmrc: | registry=<registry_link> //<registry_link>:_authToken=<auth_token>Replace
<release_name>with your Helm release name. This name is a unique identifier for each chart installation in the Kubernetes cluster.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 by 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 you build your custom plugin with a compatible version of Backstage. In Developer Hub, click Settings. Your custom plugin must be compatible with the Backstage Version (or the closest earlier version) that the Metadata section of Red Hat Developer Hub displays.
For example, if you view the history of the
backstage.jsonfile for the Entity Feedback plugin, the1fc87decommit is the closest earlier version to Backstage version of 1.39.1.
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: You need either podman or docker to package the plugin into an OCI image and to log in to registries.
- Container registry access: Access to an OCI-compliant container registry, such as the internal OpenShift registry or a public registry such as 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 dynamic loading 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 front-end plugin by using the Red Hat Developer Hub CLI. The following command uses the plugin files in the
distfolder that theyarn build:allcommand generated, and creates a newdist-scalprumfolder that has 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 front-end plugin, it uses a default Scalprum configuration if one is not found. The Scalprum configuration specifies the plugin entry point and exports, and then builds a
dist-scalprumfolder that has the dynamic plugin. The following example shows the default Scalprum configuration. However, you can add ascalprumkey to thepackage.jsonfile used by your plugin to set custom values, if necessary:{ "name": "backstage-community.plugin-entity-feedback", "exposedModules": { "PluginRoot": "./src/index.ts" } }Red Hat Developer Hub uses the following
plugin-manifest.jsonfile to load the plugin. This file is 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. Backend plugins do not require Scalprum, and the export generates a
dist-dynamicfolder 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 publish two container images to your container registry.
The following image shows the container images published to Quay.io:
3.5. Add a custom dynamic plugin to Red Hat Developer Hub Copy linkLink copied to clipboard!
Use this procedure to add custom dynamic plugins to Red Hat Developer Hub by updating the dynamic-plugins.yaml configuration file.
Procedure
To add your custom dynamic plugins to Red Hat Developer Hub, update the
dynamic-plugins.yamlfile by using the following configuration that thenpx @red-hat-developer-hub/cli@latest plugin packagecommand generates:plugins: - package: oci://quay.io/_<user_name>_/entity-feedback-plugin:0.5.0 disabled: false - package: oci://quay.io/_<user_name>_/entity-feedback-plugin-backend:0.6.0 disabled: falseNoteEnsure 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.6. Display the front-end plugin Copy linkLink copied to clipboard!
Use this procedure to configure and display a front-end plugin in the Red Hat Developer Hub UI.
Procedure
Update the
pluginConfigsection of yourdynamic-plugins.yamlfile to specify how to add the Entity Feedback to the Red Hat Developer Hub UI.dynamic-plugins.yamlfile fragment- package: oci://quay.io/_<user_name>_/entity-feedback-plugin:0.5.0 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:mountPoints- This array defines the following configurations to mount React components exposed by the plugin:
-
This configuration adds the
StarredRatingButtonscomponent to the new Feedback tab defined in entityTabs. -
Similar to the
StarredRatingButtons, this configuration mounts theEntityFeedbackResponseContenton the Feedback tab. -
This configuration adds the
StarredRatingButtonsto the default Overview tab for each entity. - 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 install and enable 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 offer feedback, as follows:
The system does not save user feedback if you log in as the Guest user.