Tech Radar logo

Backstage Tech Radar Plugin

Created by Spotify

Tech Radar maps your technology choices in one view. It groups items into quadrants like languages, frameworks, infrastructure, and processes. Rings show maturity such as Adopt, Trial, Assess, and Hold. The point is shared guidance with a clear status for each tool. It also captures how things move over time.

The Tech Radar plugin brings this model into Backstage. It renders an interactive radar inside your portal. Engineers can search, filter, open links, and read context. Entries can include descriptions and a timeline of changes. You can run one radar or many. Teams use it to align standards, plan migrations, and highlight what to avoid.

Companies use this plugin today. Spotify says it “provides a visual and concise summary of technologies in use at your organization.”.

If you want a single place to see tech decisions and their status, this plugin gives you that view inside Backstage. It makes choices visible. It reduces thrash. It helps teams move with confidence.

A screenshot of the Tech Radar plugin.

Installation Instructions

These instructions apply to self-hosted Backstage only. To use this plugin on Roadie, visit the docs.

Install the frontend package

  1. From your Backstage root directory run
Copy
yarn --cwd packages/app add @backstage-community/plugin-tech-radar

Add the page route in a legacy frontend app

  1. Edit packages/app/src/App.tsx
  2. Import the page
Copy
// packages/app/src/App.tsx
import React from 'react';
import { FlatRoutes, Route } from '@backstage/core-app-api';
import { TechRadarPage } from '@backstage-community/plugin-tech-radar';
// other imports

export const AppRoutes = () => (
  <FlatRoutes>
    {/* other routes */}
    <Route path="/tech-radar" element={<TechRadarPage />} />
  </FlatRoutes>
);

You can customize the page if you want

Copy
// packages/app/src/App.tsx
<Route
  path="/tech-radar"
  element={
    <TechRadarPage
      title="Tech Radar"
      subtitle="Engineering choices"
      pageTitle="Tech Radar"
      width={1200}
      height={800}
    />
  }
/>

Add the page using the new frontend system

  1. Import the page extension and register it
Copy
// packages/app/src/App.tsx
import React from 'react';
import { createApp, createFrontendModule } from '@backstage/frontend-app-api';
import { techRadarPage } from '@backstage-community/plugin-tech-radar/alpha';

export const app = createApp({
  features: [
    createFrontendModule({
      pluginId: 'app',
      extensions: [
        techRadarPage({
          path: '/tech-radar',
          // optional presentation settings
          title: 'Tech Radar',
          subtitle: 'Engineering choices',
          pageTitle: 'Tech Radar',
          width: 1200,
          height: 800,
        }),
      ],
    }),
  ],
});

Install the backend plugin in a legacy backend app

The frontend defaults to loading data from the tech radar backend. Install it.

  1. Add the package to the backend
Copy
yarn --cwd packages/backend add @backstage-community/plugin-tech-radar-backend
  1. Create a router loader
Copy
// packages/backend/src/plugins/techRadar.ts
import { createRouter } from '@backstage-community/plugin-tech-radar-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';

export default async function createPlugin(
  env: PluginEnvironment,
): Promise<Router> {
  return await createRouter({
    logger: env.logger,
    config: env.config,
    discovery: env.discovery,
    cache: env.cache,
    reader: env.reader,
  });
}
  1. Wire it in the backend index
Copy
// packages/backend/src/index.ts
import techRadar from './plugins/techRadar';
// other imports

async function main() {
  // existing code that creates apiRouter and envs
  const techRadarEnv = useHotMemoize(module, () => createEnv('tech-radar'));
  apiRouter.use('/tech-radar', await techRadar(techRadarEnv));

  // existing server start
}
  1. Configure a data source url so the backend can fetch the radar data
Copy
# app-config.yaml
techRadar:
  url: https://example.com/path/to/tech-radar.json

Place a JSON at that url that follows the model shown further below.

Install the backend plugin in the new backend system

  1. Add the package
Copy
yarn --cwd packages/backend add @backstage-community/plugin-tech-radar-backend
  1. Register the module in your backend builder
Copy
// packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';
import { techRadarModule } from '@backstage-community/plugin-tech-radar-backend';

const backend = createBackend();
backend.add(techRadarModule());
backend.start();
  1. Add config for the data source
Copy
# app-config.yaml
techRadar:
  url: https://example.com/path/to/tech-radar.json

Note that your module name may differ in some templates. If your package exports a different module factory name, import that one instead.

Use a custom data source without installing the backend

You can provide your own client that implements the TechRadarApi. This replaces the default backend client.

Legacy frontend app

Copy
// packages/app/src/lib/MyClient.ts
import {
  TechRadarApi,
} from '@backstage-community/plugin-tech-radar';
import { TechRadarLoaderResponse } from '@backstage-community/plugin-tech-radar-common';

export class MyOwnClient implements TechRadarApi {
  async load(id: string | undefined): Promise<TechRadarLoaderResponse> {
    const data = await fetch('https://mydata.example.com/radar.json').then(r => r.json());
    return {
      ...data,
      entries: data.entries.map((entry: any) => ({
        ...entry,
        timeline: entry.timeline.map((t: any) => ({
          ...t,
          date: new Date(t.date),
        })),
      })),
    };
  }
}

Register the API factory

Copy
// packages/app/src/apis.ts
import { createApiFactory } from '@backstage/core-plugin-api';
import { techRadarApiRef } from '@backstage-community/plugin-tech-radar';
import { MyOwnClient } from './lib/MyClient';

export const apis = [
  createApiFactory(techRadarApiRef, new MyOwnClient()),
];

New frontend system

Copy
// packages/app/src/App.tsx
import { ApiBlueprint, createApp, createFrontendModule } from '@backstage/frontend-app-api';
import { techRadarApiRef } from '@backstage-community/plugin-tech-radar';
import { MyOwnClient } from './lib/MyClient';

const techRadarApi = ApiBlueprint.make({
  name: 'techRadarApi',
  params: {
    factory: createApiFactory(techRadarApiRef, new MyOwnClient()),
  },
});

export const app = createApp({
  features: [
    createFrontendModule({
      pluginId: 'app',
      extensions: [techRadarApi],
    }),
  ],
});

Use the component directly in a custom page

You can render the component in your own page if you prefer that layout.

Create a page

Copy
// packages/app/src/components/TechRadar/CustomTechRadarPage.tsx
import React from 'react';
import { Content, Page, Header } from '@backstage/core-components';
import { TechRadarComponent } from '@backstage-community/plugin-tech-radar';

export const CustomTechRadarPage = () => (
  <Page themeId="tool">
    <Header title="Tech Radar" />
    <Content>
      <TechRadarComponent width={1200} height={800} />
    </Content>
  </Page>
);

Add a route to it

Copy
// packages/app/src/App.tsx
import { CustomTechRadarPage } from './components/TechRadar/CustomTechRadarPage';

<FlatRoutes>
  {/* other routes */}
  <Route path="/tech-radar" element={<CustomTechRadarPage />} />
</FlatRoutes>

Data model example

The backend and the custom client both expect the same shape. Here is a minimal sample.

Copy
{
  "title": "Engineering Tech Radar",
  "quadrants": [
    { "id": "1", "name": "Bottom right" },
    { "id": "2", "name": "Bottom left" },
    { "id": "3", "name": "Top left" },
    { "id": "4", "name": "Top right" }
  ],
  "rings": [
    { "id": "adopt", "name": "ADOPT", "color": "#93c47d" },
    { "id": "trial", "name": "TRIAL", "color": "#93d2c2" },
    { "id": "assess", "name": "ASSESS", "color": "#fbdb84" },
    { "id": "hold", "name": "HOLD", "color": "#efafa9" }
  ],
  "entries": [
    {
      "id": "typescript",
      "title": "Typescript",
      "description": "Long description for Typescript",
      "key": "typescript",
      "url": "#",
      "quadrant": "1",
      "timeline": [
        { "moved": 1, "ringId": "adopt", "date": "2022-02-08", "description": "Adopted" },
        { "moved": 0, "ringId": "trial", "date": "2022-02-06", "description": "Trial" }
      ]
    }
  ]
}

Things to Know

  • The frontend will use the backend by default. If you register a custom TechRadarApi, your client will be used instead.
  • The page supports an optional id prop. Use it if you need multiple radars, for example .

How do I load in my own data?

To pass own data to plugin use a getData prop which expects a Promise<TechRadarLoaderResponse> signature.

For example:

Copy
const getData = () =>
  Promise.resolve({
    quadrants: [{ id: 'infrastructure', name: 'Infrastructure' }],
    rings: [{ id: 'use', name: 'USE', color: '#91c49d' }],
    entries: [
      {
        moved: 0,
        ring: 'use',
        url: '#',
        key: 'firebase-function',
        id: 'firebase-function',
        title: 'FireBase Function',
        quadrant: 'infrastructure',
      },
    ],
  });

<TechRadarComponent width={1500} height={900} getData={getData} />;

Changelog

This changelog is produced from commits made to the Tech Radar plugin since a year ago, and based on the code located here. It may not contain information about all commits. Releases and version bumps are intentionally omitted. This changelog is generated by AI.

Breaking changes

  • Introduce Tech Radar backend and common package. This is a breaking change. Add the common package to your app. Update imports if you use the types. The default client now tries the backend then falls back to mock data. #1539 (merged 11 months ago)

Features

  • Render Markdown in timeline entry descriptions. #4018 (merged 3 months ago)
  • Improve Tech Radar responsiveness on small screens. Columns keep a minimum width. The legend shrinks from three to one column as needed. The radar resizes with the screen. It stays centered between the legends. #2474 (merged 5 months ago)
  • Make the new Tech Radar common and backend packages public. #1762 (merged 11 months ago)

Documentation

  • Fix README link to the detailed example. #4020 (merged 4 months ago)
  • Improve Tech Radar docs and sample app setup. #1762 (merged 11 months ago)

Dependencies

  • Update types color to v4. #2231 (merged 9 months ago)
  • Remove unused canvas dev dependency. #3565 (merged 5 months ago)

Maintenance

  • Reduce knip false positives by updating repo tools. #3018 (merged 6 months ago)
  • Clarify if else flow with a no op change. #2520 (merged 8 months ago)

Set up Backstage in minutes with Roadie