DevTools logo

Backstage DevTools Plugin

Created by Keyloop

DevTools is a Backstage plugin that surfaces key facts about your portal right in the UI. It gives you a clear view of what is running so you can troubleshoot faster and understand your setup with less guesswork. It lives in the Backstage project.

The Info tab shows details about the host and runtime. You can see the operating system, the Node.js version, the Backstage version, and which packages are present. This helps when you need to confirm versions or compare environments during an incident.The Config tab displays the effective configuration your instance is using. It respects plugin config schemas to mask fields marked as secret when that metadata is present. This makes it easier to review config in one place while keeping sensitive values out of view for most users.

You can add optional tabs to suit your needs. A common addition checks external dependencies from the running instance to show basic reachability. There is also an optional view for unprocessed catalog entities that can help with data quality work. Teams can customize the layout to add or remove tabs as their use cases evolve.If you run a self hosted Backstage instance, DevTools gives you a quick way to see what is really deployed and how it is configured. It is a simple way to cut the time you spend chasing environment drift.

Installation Instructions

These instructions apply to self-hosted Backstage only.

Install the backend with the new backend system

  1. Add the backend package

    Copy
    yarn --cwd packages/backend add @backstage/plugin-devtools-backend
  2. Register the backend plugin

    Open packages/backend/src/index.ts

    Add the module to your backend

    Copy
    // inside createBackend setup
    backend.add(import('@backstage/plugin-devtools-backend'));
  3. Start your backend

    Copy
    yarn --cwd packages/backend start

Install the backend with the classic backend system

  1. Add the backend package

    Copy
    yarn --cwd packages/backend add @backstage/plugin-devtools-backend
  2. Create the router

    Create a new file at packages/backend/src/plugins/devtools.ts

    Copy
    import { createRouter } from '@backstage/plugin-devtools-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,
        permissions: env.permissions,
      });
    }
  3. Mount the router

    Open packages/backend/src/index.ts

    Add the plugin env and mount the route

    Copy
    import devtools from './plugins/devtools';
    
    // create an env for the plugin, pattern matches other plugins in your file
    const devtoolsEnv = useHotMemoize(module, () => createEnv('devtools'));
    
    // mount the router
    apiRouter.use('/devtools', await devtools(devtoolsEnv));
  4. Start your backend

    Copy
    yarn --cwd packages/backend start

Install the frontend

  1. Add the frontend package

    Copy
    yarn --cwd packages/app add @backstage/plugin-devtools
  2. Add the route

    Open packages/app/src/App.tsx

    Import the page

    Copy
    import { DevToolsPage } from '@backstage/plugin-devtools';

    Add the route before the closing FlatRoutes tag

    Copy
    <Route path="/devtools" element={<DevToolsPage />} />
  3. Add the sidebar link

    Open packages/app/src/components/Root/Root.tsx

    Import the icon

    Copy
    import BuildIcon from '@material-ui/icons/Build';

    Add the sidebar item after SidebarSettings

    Copy
    <SidebarItem icon={BuildIcon} to="devtools" text="DevTools" />
  4. Start your app

    Copy
    yarn start

Optional tabs in the frontend

Add External Dependencies

  1. Create a custom page file

    Create packages/app/src/components/devtools/CustomDevToolsPage.tsx

    Copy
    import {
      ConfigContent,
      ExternalDependenciesContent,
      InfoContent,
      DevToolsLayout,
    } from '@backstage/plugin-devtools';
    
    export const DevToolsPage = () => {
      return (
        <DevToolsLayout>
          <DevToolsLayout.Route path="info" title="Info">
            <InfoContent />
          </DevToolsLayout.Route>
          <DevToolsLayout.Route path="config" title="Config">
            <ConfigContent />
          </DevToolsLayout.Route>
          <DevToolsLayout.Route
            path="external-dependencies"
            title="External Dependencies"
          >
            <ExternalDependenciesContent />
          </DevToolsLayout.Route>
        </DevToolsLayout>
      );
    };
    
    export const customDevToolsPage = <DevToolsPage />;
  2. Use the custom page in routing

    Open packages/app/src/App.tsx

    Import the custom page

    Copy
    import { customDevToolsPage } from './components/devtools/CustomDevToolsPage';

    Wrap the route

    Copy
    <Route path="/devtools" element={<DevToolsPage />}>
      {customDevToolsPage}
    </Route>
  3. Start your app

    Copy
    yarn start

Add Catalog Unprocessed Entities tab

  1. Install the plugin as per its documentation

  2. Import its content into your custom page

    Copy
    import { UnprocessedEntitiesContent } from '@backstage/plugin-catalog-unprocessed-entities';
  3. Add the route to your custom layout

    Copy
    <DevToolsLayout.Route path="unprocessed-entities" title="Unprocessed Entities">
      <UnprocessedEntitiesContent />
    </DevToolsLayout.Route>

Permissions setup

You can secure the sidebar entry or the route or both

Secure the sidebar entry

  1. Add the common package

    Copy
    yarn --cwd packages/app add @backstage/plugin-devtools-common
  2. Update the sidebar

    Open packages/app/src/components/Root/Root.tsx

    Import the helpers

    Copy
    import { devToolsAdministerPermission } from '@backstage/plugin-devtools-common';
    import { RequirePermission } from '@backstage/plugin-permission-react';

    Wrap the sidebar item

    Copy
    <RequirePermission permission={devToolsAdministerPermission} errorPage={<></>}>
      <SidebarItem icon={BuildIcon} to="devtools" text="DevTools" />
    </RequirePermission>

Secure the route

  1. Add the common package if not already added

    Copy
    yarn --cwd packages/app add @backstage/plugin-devtools-common
  2. Wrap the route

    Open packages/app/src/App.tsx

    Import the permission

    Copy
    import { devToolsAdministerPermission } from '@backstage/plugin-devtools-common';
    import { RequirePermission } from '@backstage/plugin-permission-react';

    Wrap the route element

    Copy
    <Route
      path="/devtools"
      element={
        <RequirePermission permission={devToolsAdministerPermission}>
          <DevToolsPage />
        </RequirePermission>
      }
    />
  3. If you use a customDevToolsPage then wrap the element and keep the children

    Copy
    <Route
      path="/devtools"
      element={
        <RequirePermission permission={devToolsAdministerPermission}>
          <DevToolsPage />
        </RequirePermission>
      }
    >
      {customDevToolsPage}
    </Route>

Example backend permission policy

  1. Add the common package to the backend

    Copy
    yarn --cwd packages/backend add @backstage/plugin-devtools-common
  2. Import the permissions where you define your policy

    Copy
    import {
      devToolsAdministerPermission,
      devToolsConfigReadPermission,
      devToolsExternalDependenciesReadPermission,
      devToolsInfoReadPermission,
    } from '@backstage/plugin-devtools-common';
  3. Sample policy code

    Copy
    // packages/backend/src/plugins/permission.ts
    import {
      AuthorizeResult,
      isPermission,
      PermissionPolicy,
      PolicyDecision,
      PolicyQuery,
    } from '@backstage/plugin-permission-node';
    import {
      devToolsAdministerPermission,
      devToolsConfigReadPermission,
      devToolsExternalDependenciesReadPermission,
      devToolsInfoReadPermission,
    } from '@backstage/plugin-devtools-common';
    
    class TestPermissionPolicy implements PermissionPolicy {
      async handle(request: PolicyQuery): Promise<PolicyDecision> {
        const { user } = request;
    
        if (isPermission(request.permission, devToolsAdministerPermission)) {
          if (
            user?.identity.ownershipEntityRefs.includes(
              'group:default/backstage-admins',
            )
          ) {
            return { result: AuthorizeResult.ALLOW };
          }
          return { result: AuthorizeResult.DENY };
        }
    
        if (isPermission(request.permission, devToolsInfoReadPermission)) {
          if (
            user?.identity.ownershipEntityRefs.includes(
              'group:default/backstage-admins',
            )
          ) {
            return { result: AuthorizeResult.ALLOW };
          }
          return { result: AuthorizeResult.DENY };
        }
    
        if (isPermission(request.permission, devToolsConfigReadPermission)) {
          if (
            user?.identity.ownershipEntityRefs.includes(
              'group:default/backstage-admins',
            )
          ) {
            return { result: AuthorizeResult.ALLOW };
          }
          return { result: AuthorizeResult.DENY };
        }
    
        if (
          isPermission(
            request.permission,
            devToolsExternalDependenciesReadPermission,
          )
        ) {
          if (
            user?.identity.ownershipEntityRefs.includes(
              'group:default/backstage-admins',
            )
          ) {
            return { result: AuthorizeResult.ALLOW };
          }
          return { result: AuthorizeResult.DENY };
        }
    
        return { result: AuthorizeResult.ALLOW };
      }
    }
    
    export const permissionPolicy = new TestPermissionPolicy();

Configuration

Show extra package versions on the Info tab

Add extra prefixes to your app config

Copy
devTools:
  info:
    packagePrefixes:
      - '@roadiehq/backstage-'
      - '@spotify/backstage-'

Configure External Dependencies

Add endpoints to your app config

Copy
devTools:
  externalDependencies:
    endpoints:
      - name: 'Google'
        type: 'fetch'
        target: 'https://google.ca'
      - name: 'Google Public DNS'
        type: 'ping'
        target: '8.8.8.8'

If you use the ping type then make sure ping exists in the host OS that runs your backend

Example Dockerfile step

Copy
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
    --mount=type=cache,target=/var/lib/apt,sharing=locked \
    apt-get update && \
    apt-get install -y iputils-ping

Backstage version reporting

The Info tab reads the Backstage version from backstage.json at the root of your instance

Copy it into your image during build

Copy
WORKDIR /app
ENV NODE_ENV=production

# include backstage.json in the image
COPY --chown=node:node backstage.json ./

Summary of imports to show the UI

  • In App.tsx import and route

    Copy
    import { DevToolsPage } from '@backstage/plugin-devtools';
    
    <Route path="/devtools" element={<DevToolsPage />} />
  • In Root.tsx add the sidebar link

    Copy
    import BuildIcon from '@material-ui/icons/Build';
    
    <SidebarItem icon={BuildIcon} to="devtools" text="DevTools" />

Changelog

This changelog is produced from commits made to the DevTools 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

  • Simplify FrontendPlugin type. Override methods move to the new OverridableFrontendPlugin type. Update any code that uses AppNode overrides to use the new type. #30904 2 months ago
  • ApiBlueprint now uses callback param syntax with defineParams. Update blueprint usage to the new pattern. #30673 2 months ago
  • Remove the default prefix from blueprint params. Update param names in any custom blueprints. #30721 2 months ago
  • Rename the plugin ID option to pluginId in createFrontendPlugin. Update any calls in this repo. #29749 5 months ago

Features

  • Add plugin info support. Plugins can expose metadata from package JSON and an optional manifest. Apps can customize how this is read. #29953 4 months ago
  • Add a new repo start command for local workspaces. It can start the app and backend together or a single target. This helps when running a community plugin in isolation. #29489 5 months ago

Documentation

  • Recommend the name defineParams for the blueprint param helper. This lines up naming across definition and use. #30714 2 months ago

Maintenance

  • Avoid forwarding ConfigurableExtensionDataRef in types. Cleans up API reports. #30765 2 months ago
  • Update React imports to the modern JSX transform. Prepares for React nineteen. #29499 5 months ago
  • Bump TypeScript to five point six. #29090 7 months ago
  • Sort extension definition in plugin types for more stable API reports. #29061 7 months ago
  • Fix API report warning validation. Reports now warn as expected. #27116 11 months ago

Set up Backstage in minutes with Roadie