Backstage plugin development 101: introduction and a basic example

December 21, 2022

Backstage plugin development 101: introduction and a basic example

Ready to start?

What is backstage?

Backstage is an open source project developed by Spotify and donated to the CNCF. It has pioneered the platform engineering revolution, with internal developer platforms at the core. There are two main components to the backstage developer portal - the software catalog and the ability to deliver developer self-service actions. The goal of backstage is to organize infrastructure through the creation of the catalog, which shows everything in context - tools, data, microservices, APIs and documentation. One of the key differentiators of backstage is its flexibility, enabling the monitoring of any type of architecture and environment.

Why do plugins matter in backstage?

At the core of Backstage are the backstage plugins, that allow platform engineering teams to make backstage deliver what they need. This led to the creation of a marketplace for plugins and led to significant backstage plugin development. Recently, backstage has announced that although backstage is open sourced, five plugins will be sold through a paid subscription, pointing to where backstage believes there is extra value delivered by it.

Three plugins worth mentioning are:

  • Insights, which is generally the ability to query the backstage software catalog and the ability to create reporting on top of it
  • Soundcheck, which lets users create scorecards about metrics related to services and infrastructure, allowing the monitoring of metrics such as DORA metrics or production readiness.
  • RBAC - role based access control which affects who sees what in backstage, and what actions can be taken depending on the users’ characteristics. This is important, with regards to the data in backstage as well as the plugins themselves.

Backstage is a plugin playground

Plugins are essentially the heart of Backstage and what makes its developer portal unique. Almost every page is a plugin, including the home page. It’s a plugin playground.

The plugin system lets you extend Backstage with features and integrations. For example, the “Todo” plugin lists TODO comments from your source code.

How plugins integrate with Backstage

To understand how plugins integrate with Backstage, we first need to understand the basic structure of Backstage. Backstage is comprised of the following:

  1. React Frontend, using Typescript;
  2. NodeJS+express Backend, using Typescript.

Plugins can extend the backend or the frontend (or both). If you want to extend the frontend you typically need to implement a React component and if you want to extend the backend you need to implement Rest API routes.

Let’s dive deeper and learn how to implement each extension:

Backstage Plugins Tutorial

First, clone backstage locally and install dependencies:

git clone --depth 1 git@github.com:backstage/backstage.git
cd backstage
yarn install

Next, we’ll use Backstage’s cli tool to scaffold an example plugin:

(when asked to enter plugin id, enter ‘my-plugin’)

yarn new --select plugin

The latter command creates a plugin in its own directory and integrates it into backstage. There is no black magic here, you can see the plugin under `plugins/<plugin-id>` and in order to add the plugin page to backstage’s app, the command also adds another route entry to `packages/app/src/tsx`. You can run git diff to see it for yourself:

git diff packages/app/src/App.tsx

To run Backstage and see our plugin in action, run:

`yarn run dev`

Which will run both the frontend and backend of Backstage and then we can see our plugin at localhost:3000/my-plugin

The example plugin uses the random user api to display a list of users. We can learn a few things from this example:

1. Backstage comes with a core lib filled with UI components. If we open `plugins/my-plugin/src/components/ExampleComponent/ExampleComponent.tsx` We can see many UI components being imported:

import {
 InfoCard,
 Header,
 Page,
 Content,
 ContentHeader,
 HeaderLabel,
 SupportButton,
} from '@backstage/core-components';

2. Since backstage comes as a source code project to self host and we have access to all the code, we can essentially modify the code in any way shape or form. Because of that, it is important to follow the conventions and best practices provided by Backstage. For example, to load a plugin we use a dynamic import to keep our web app lean and fast (see plugins/my-plugin/src/plugin.ts:31

export const MyPluginPage = myPluginPlugin.provide(
 createRoutableExtension({
   name: 'MyPluginPage',
   component: () =>
     import('./components/ExampleComponent').then(m => m.ExampleComponent),
   mountPoint: rootRouteRef,
 }),
);

3. Again, there is no magic here, the fact that `localhost:3000/my-plugin` renders our plugin is because we added (using the backstage cli tool) the route `my-plugin` to the app’s router (`packages/app/src/App.tsx`)

<Route path="/apache-airflow" element={<ApacheAirflowPage />} />
   <Route path="/my-plugin" element={<MyPluginPage />} />
 </FlatRoutes>

To showcase how flexible Backstage is, we’ll modify the plugin to show Port inside of backstage. For the next step to succeed, make sure you are signed-up and logged-in to Port (getport.io)

First, open `plugins/my-plugin/src/components/ExampleComponent/ExampleComponent.tsx` and replace the component with the following code:

export const ExampleComponent = () => {
 return (
   <iframe
     title="port"
     src="https://app.getport.io"
     style={{
       border: 'none',
       height: '100vh',
       width: '100%',
       resize: 'both',
       overflow: 'auto',
     }}
   />
 );
};

Rebuild the code by running (inside the plugin folder)

yarn run build

 And refresh the page https://localhost:3000/my-plugin

You should be able to see Port inside of Backstage, how cool is that?

{{cta_7}}

Conclusion

The Backstage plugin system is very flexible, but it demands managing the code by yourself and customizing internal files.

On the one hand, there is nothing you can’t do inside Backstage, but on the other hand the flexibility comes from handcrafting the app’s code, which requires coding skills and familiarity with React, NodeJS and Typescript. This makes Backstage more compatible for technical teams who are willing to code, and have the capacity and time for it, but may be cumbersome for others.

{{cta_1}}

Check out Port's pre-populated demo and see what it's all about.

Check live demo

No email required

{{cta_2}}

Contact sales for a technical product walkthrough

Let’s start
{{cta_3}}

Open a free Port account. No credit card required

Let’s start
{{cta_4}}

Watch Port live coding videos - setting up an internal developer portal & platform

Let’s start
{{cta_5}}

Check out Port's pre-populated demo and see what it's all about.

(no email required)

Let’s start
{{cta_6}}

Contact sales for a technical product walkthrough

Let’s start
{{cta_7}}

Open a free Port account. No credit card required

Let’s start
{{cta_8}}

Watch Port live coding videos - setting up an internal developer portal & platform

Let’s start
{{cta-demo}}
{{reading-box-backstage-vs-port}}

Example JSON block

{
  "foo": "bar"
}

Order Domain

{
  "properties": {},
  "relations": {},
  "title": "Orders",
  "identifier": "Orders"
}

Cart System

{
  "properties": {},
  "relations": {
    "domain": "Orders"
  },
  "identifier": "Cart",
  "title": "Cart"
}

Products System

{
  "properties": {},
  "relations": {
    "domain": "Orders"
  },
  "identifier": "Products",
  "title": "Products"
}

Cart Resource

{
  "properties": {
    "type": "postgress"
  },
  "relations": {},
  "icon": "GPU",
  "title": "Cart SQL database",
  "identifier": "cart-sql-sb"
}

Cart API

{
 "identifier": "CartAPI",
 "title": "Cart API",
 "blueprint": "API",
 "properties": {
   "type": "Open API"
 },
 "relations": {
   "provider": "CartService"
 },
 "icon": "Link"
}

Core Kafka Library

{
  "properties": {
    "type": "library"
  },
  "relations": {
    "system": "Cart"
  },
  "title": "Core Kafka Library",
  "identifier": "CoreKafkaLibrary"
}

Core Payment Library

{
  "properties": {
    "type": "library"
  },
  "relations": {
    "system": "Cart"
  },
  "title": "Core Payment Library",
  "identifier": "CorePaymentLibrary"
}

Cart Service JSON

{
 "identifier": "CartService",
 "title": "Cart Service",
 "blueprint": "Component",
 "properties": {
   "type": "service"
 },
 "relations": {
   "system": "Cart",
   "resources": [
     "cart-sql-sb"
   ],
   "consumesApi": [],
   "components": [
     "CorePaymentLibrary",
     "CoreKafkaLibrary"
   ]
 },
 "icon": "Cloud"
}

Products Service JSON

{
  "identifier": "ProductsService",
  "title": "Products Service",
  "blueprint": "Component",
  "properties": {
    "type": "service"
  },
  "relations": {
    "system": "Products",
    "consumesApi": [
      "CartAPI"
    ],
    "components": []
  }
}

Component Blueprint

{
 "identifier": "Component",
 "title": "Component",
 "icon": "Cloud",
 "schema": {
   "properties": {
     "type": {
       "enum": [
         "service",
         "library"
       ],
       "icon": "Docs",
       "type": "string",
       "enumColors": {
         "service": "blue",
         "library": "green"
       }
     }
   },
   "required": []
 },
 "mirrorProperties": {},
 "formulaProperties": {},
 "calculationProperties": {},
 "relations": {
   "system": {
     "target": "System",
     "required": false,
     "many": false
   },
   "resources": {
     "target": "Resource",
     "required": false,
     "many": true
   },
   "consumesApi": {
     "target": "API",
     "required": false,
     "many": true
   },
   "components": {
     "target": "Component",
     "required": false,
     "many": true
   },
   "providesApi": {
     "target": "API",
     "required": false,
     "many": false
   }
 }
}

Resource Blueprint

{
 “identifier”: “Resource”,
 “title”: “Resource”,
 “icon”: “DevopsTool”,
 “schema”: {
   “properties”: {
     “type”: {
       “enum”: [
         “postgress”,
         “kafka-topic”,
         “rabbit-queue”,
         “s3-bucket”
       ],
       “icon”: “Docs”,
       “type”: “string”
     }
   },
   “required”: []
 },
 “mirrorProperties”: {},
 “formulaProperties”: {},
 “calculationProperties”: {},
 “relations”: {}
}

API Blueprint

{
 "identifier": "API",
 "title": "API",
 "icon": "Link",
 "schema": {
   "properties": {
     "type": {
       "type": "string",
       "enum": [
         "Open API",
         "grpc"
       ]
     }
   },
   "required": []
 },
 "mirrorProperties": {},
 "formulaProperties": {},
 "calculationProperties": {},
 "relations": {
   "provider": {
     "target": "Component",
     "required": true,
     "many": false
   }
 }
}

Domain Blueprint

{
 "identifier": "Domain",
 "title": "Domain",
 "icon": "Server",
 "schema": {
   "properties": {},
   "required": []
 },
 "mirrorProperties": {},
 "formulaProperties": {},
 "calculationProperties": {},
 "relations": {}
}

System Blueprint

{
 "identifier": "System",
 "title": "System",
 "icon": "DevopsTool",
 "schema": {
   "properties": {},
   "required": []
 },
 "mirrorProperties": {},
 "formulaProperties": {},
 "calculationProperties": {},
 "relations": {
   "domain": {
     "target": "Domain",
     "required": true,
     "many": false
   }
 }
}
{{tabel-1}}

Microservices SDLC

  • Scaffold a new microservice

  • Deploy (canary or blue-green)

  • Feature flagging

  • Revert

  • Lock deployments

  • Add Secret

  • Force merge pull request (skip tests on crises)

  • Add environment variable to service

  • Add IaC to the service

  • Upgrade package version

Development environments

  • Spin up a developer environment for 5 days

  • ETL mock data to environment

  • Invite developer to the environment

  • Extend TTL by 3 days

Cloud resources

  • Provision a cloud resource

  • Modify a cloud resource

  • Get permissions to access cloud resource

SRE actions

  • Update pod count

  • Update auto-scaling group

  • Execute incident response runbook automation

Data Engineering

  • Add / Remove / Update Column to table

  • Run Airflow DAG

  • Duplicate table

Backoffice

  • Change customer configuration

  • Update customer software version

  • Upgrade - Downgrade plan tier

  • Create - Delete customer

Machine learning actions

  • Train model

  • Pre-process dataset

  • Deploy

  • A/B testing traffic route

  • Revert

  • Spin up remote Jupyter notebook

{{tabel-2}}

Engineering tools

  • Observability

  • Tasks management

  • CI/CD

  • On-Call management

  • Troubleshooting tools

  • DevSecOps

  • Runbooks

Infrastructure

  • Cloud Resources

  • K8S

  • Containers & Serverless

  • IaC

  • Databases

  • Environments

  • Regions

Software and more

  • Microservices

  • Docker Images

  • Docs

  • APIs

  • 3rd parties

  • Runbooks

  • Cron jobs

Starting with Port is simple, fast and free.

Let’s start