Backstage Plugins by Example: Part 1

John Tucker
6 min readMar 6, 2022

--

A complete walk through of creating a non-trivial Backstage Plugin.

This article builds upon the series of articles starting with Backstage by Example (Part 1).

Backstage comes with a number of core features, e.g., Software Catalog, Kubernetes, Software Templates, Backstage Search, and TechDocs. The most powerful feature of Backstage, however, is the ability to relatively easily create new features using Backstage Plugins.

Backstage plugins provide features to a Backstage App.

Each plugin is treated as a self-contained web app and can include almost any type of content. Plugins all use a common set of platform APIs and reusable UI components. Plugins can fetch data from external sources using the regular browser APIs or by depending on external modules to do the work.

Plugin Development

While there are number of open-source Plugins available, we are going to explore creating our own Plugin. To make it a bit more challenging, our finished Plugin will allow us to associate an AWS S3 Bucket with a Backstage Component and browse its contents from the Component’s Backstage App UI.

Create a Backstage Plugin

Here we start with the instructions provided in Backstage’s Create a Backstage Plugin. As we have been running the Backstage App in a container, we need to go through the process of re-building / starting the image after we make changes.

Navigating to the URL http://localhost:7007/my-plugin , we indeed see the newly created plugin page. One problem is the missing images; looking at the Browser’s console we can see this is another manifestation of the Content Policy issue that we addressed in Backstage by Example (Part 3). We are not going to worry about this as we going to refactoring this generated plugin.

Another interesting observation is that in the upper-right, it shows an Owner of Team X; but we neither have a User or Group with this name. Looking at the generated code in plugins/my-plugin/src/components/ExampleComponent/ExampleComponent.tsx we indeed see that Team X is simply hardcoded into the UI.

Barebones Plugin

When creating our plugin, the CLI tool scaffolded quite a number of files; including the contents of the plugins/my-plugin folder. Here we will strip down the plugin to a simplified “Hello World” version.

Rather than having to go through the lengthy process of re-building / starting the image each time we make a change to the plugin, we can rather simply run yarn start from the plugins/my-plugin folder and do live development.

We first strip down the file plugins/my-plugin/src/components/ExampleComponent/ExampleComponent.tsx to a bare-bones React “Hello World!” component.

As we have drastically changed the contents of ExampleComponent.tsx, we go ahead and remove the test file plugins/my-plugin/src/components/ExampleComponent/ExampleComponent.test.tsx.

Also, as we no longer using it, we remove the folder plugins/my-plugin/src/components/ExampleFetchComponent/.

At this point, we have our “Hello World!” plugin.

A couple of observations:

  • Instead of the more familiar use of a default export, the ExampleComponent.tsx module exports a named, ExampleComponent. This export, in turn, is imported and re-exported from the folder’s index.ts file. This is a nice pattern allow one to abstract away the folder’s file structure
  • At this point, we do not need to understand the contents of the other files in the plugins/my-plugin/src folder; we can treat them as a black box

Integrate into the Software Catalog

At this point, our plugin is not integrated into the Software Catalog; i.e., the plugin’s UI is not associated with the Backstage Components UI. The instructions provided in Backstage’s Integrate into the Software Catalog documentation point out the key steps to accomplish this.

In this first step, we keep it simple; we simply display our plugin UI as tabbed content on a Backstage service Component’s UI.

We accomplish this by removing the following lines from packages/app/src/App.tsx.

...
import { MyPluginPage } from '@internal/plugin-my-plugin';
...
<Route path="/my-plugin" element={<MyPluginPage />}/>

We then add the following lines to packages/app/src/components/catalog/EntityPage.tsx; the import at the top of the file and the EntityLayout.Route React Component a child of the EntityLayouts Component of the serviceEntityPage variable.

...
import { MyPluginPage } from '@internal/plugin-my-plugin';
...
const serviceEntityPage = (
<EntityLayout>
...
<EntityLayout.Route path="/my-plugin" title="My Plugin">
<MyPluginPage />
</EntityLayout.Route>

After re-building / starting the Backstage App image we next create a new GitHub repository, e.g., my-my-plugin-component, with a catalog-info.yaml file in it.

We than use the Create… > REGISTER EXISTING COMPONENT button to register the my-my-plugin-component service Component using the link to the catalog-info.yaml file. Navigating to the Component, we can use the MY PLUGIN tab to display our plugin’s UI.

Fixing the React Component’s Name

Now that we are building a UI for a tab within a Backstage Component (Entity), it appears that the convention is that we need to rename our React Component from MyPluginPage to EntityMyPluginContent.

Rather than trying to figure this out ourselves we can borrow the solution from the Kubernetes Plugin (a Core feature) by updating the following plugin files as shown.

plugins/my-plugin/src/routes.ts

plugins/my-plugin/src/plugin.ts

plugins/my-plugin/src/index.ts

plugins/my-plugin/dev/index.ts

With these changes we can change the use the EntityMyPluginContent Component in the file packages/app/src/components/catalog/EntityPage.tsx.

After re-building / starting the Backstage App we can indeed see that everything is working as expected.

Fixing Plugin Development

Right now when we run yarn start from the plugin folder, the plugin shows up as a page instead of tabbed content inside a Backstage Component’s UI.

Again following the example of the Kubernetes Plugin, we change plugins/my-plugin/dev/index.ts.

A couple of observations:

  • The key here is that we are wrapping our EntityMyPluginContent with an EntityProvider with a mock Entity (Component)

After restarting the development environment there are only subtle changes in the UI, but next we will see the importance of the change.

useEntity Hook

While we have associated the plugin’s UI as tabbed content within the Backstage Components UI, we have not used information from the Component (or Entity); let use address this.

From the plugins/my-plugin folder we run.

$ yarn add @backstage/plugin-catalog-react

We then update plugins/my-plugin/src/components/ExampleComponent/ExampleComponent.tsx; essentially adding and using the useEntity hook.

Restarting our development environment we see our changes in action.

After re-building / starting the Backstage App we can indeed see that everything is working as expected.

Next Steps

Now that we have stubbed in the Plugin’s frontend implementation, we switch gears to stub in the backend implementation in the next article Backstage Plugins by Example: Part 2.

--

--

John Tucker

Broad infrastructure, development, and soft-skill background