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 plugin

The backend supports only the new backend system.

  1. Add the package to your backend

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

    Copy
    // packages/backend/src/index.ts
    import { createBackend } from '@backstage/backend-defaults';
    
    const backend = createBackend();
    
    // other feature registrations
    
    backend.add(import('@backstage/plugin-devtools-backend'));
    
    backend.start();

Add the frontend plugin

  1. Add the package to your frontend app

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

    Copy
    // packages/app/src/App.tsx
    import React from 'react';
    import { Route } from 'react-router';
    import { FlatRoutes } from '@backstage/core-app-api';
    import { DevToolsPage } from '@backstage/plugin-devtools';
    
    export default function App() {
      return (
        <FlatRoutes>
          {/* other routes */}
          <Route path="/devtools" element={<DevToolsPage />} />
        </FlatRoutes>
      );
    }
  3. Add a sidebar link

    Copy
    // packages/app/src/components/Root/Root.tsx
    import React from 'react';
    import { SidebarPage, Sidebar, SidebarItem, SidebarDivider, SidebarSpace, SidebarSettings } from '@backstage/core-components';
    import BuildIcon from '@material-ui/icons/Build';
    
    export const Root = ({ children }: { children?: React.ReactNode }) => (
      <SidebarPage>
        <Sidebar>
          {/* other sidebar items */}
          <SidebarSettings />
          <SidebarItem icon={BuildIcon} to="devtools" text="DevTools" />
          <SidebarDivider />
          <SidebarSpace />
        </Sidebar>
        {children}
      </SidebarPage>
    );

Optional customize the DevTools tabs

You can choose which tabs to show. You can also add the External Dependencies tab.

  1. Create a custom page component

    Copy
    // packages/app/src/components/devtools/CustomDevToolsPage.tsx
    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 your route

    Copy
    // packages/app/src/App.tsx
    import React from 'react';
    import { Route } from 'react-router';
    import { FlatRoutes } from '@backstage/core-app-api';
    import { DevToolsPage } from '@backstage/plugin-devtools';
    import { customDevToolsPage } from './components/devtools/CustomDevToolsPage';
    
    export default function App() {
      return (
        <FlatRoutes>
          {/* other routes */}
          <Route path="/devtools" element={<DevToolsPage />}>
            {customDevToolsPage}
          </Route>
        </FlatRoutes>
      );
    }

Optional add the Catalog Unprocessed Entities tab

Follow the setup for that plugin in its docs, then add its content as a tab.

Copy
// packages/app/src/components/devtools/CustomDevToolsPage.tsx
import {
  ConfigContent,
  ExternalDependenciesContent,
  InfoContent,
  DevToolsLayout,
} from '@backstage/plugin-devtools';
import { UnprocessedEntitiesContent } from '@backstage/plugin-catalog-unprocessed-entities';

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.Route
        path="unprocessed-entities"
        title="Unprocessed Entities"
      >
        <UnprocessedEntitiesContent />
      </DevToolsLayout.Route>
    </DevToolsLayout>
  );
};

export const customDevToolsPage = <DevToolsPage />;

Optional permissions on the sidebar item

  1. Add the common package to the frontend

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

    Copy
    // packages/app/src/components/Root/Root.tsx
    import React from 'react';
    import { SidebarPage, Sidebar, SidebarItem, SidebarDivider, SidebarSpace, SidebarSettings } from '@backstage/core-components';
    import BuildIcon from '@material-ui/icons/Build';
    import { RequirePermission } from '@backstage/plugin-permission-react';
    import { devToolsAdministerPermission } from '@backstage/plugin-devtools-common';
    
    export const Root = ({ children }: { children?: React.ReactNode }) => (
      <SidebarPage>
        <Sidebar>
          <SidebarSettings />
          <RequirePermission permission={devToolsAdministerPermission} errorPage={<></>}>
            <SidebarItem icon={BuildIcon} to="devtools" text="DevTools" />
          </RequirePermission>
          <SidebarDivider />
          <SidebarSpace />
        </Sidebar>
        {children}
      </SidebarPage>
    );

Optional permissions on the route

  1. Add the common package to the frontend if you did not already

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

    Copy
    // packages/app/src/App.tsx
    import React from 'react';
    import { Route } from 'react-router';
    import { FlatRoutes } from '@backstage/core-app-api';
    import { DevToolsPage } from '@backstage/plugin-devtools';
    import { RequirePermission } from '@backstage/plugin-permission-react';
    import { devToolsAdministerPermission } from '@backstage/plugin-devtools-common';
    
    export default function App() {
      return (
        <FlatRoutes>
          {/* other routes */}
          <Route
            path="/devtools"
            element={
              <RequirePermission permission={devToolsAdministerPermission}>
                <DevToolsPage />
              </RequirePermission>
            }
          />
        </FlatRoutes>
      );
    }
  3. If you use a custom page include the children as well

    Copy
    // packages/app/src/App.tsx
    import React from 'react';
    import { Route } from 'react-router';
    import { FlatRoutes } from '@backstage/core-app-api';
    import { DevToolsPage } from '@backstage/plugin-devtools';
    import { customDevToolsPage } from './components/devtools/CustomDevToolsPage';
    import { RequirePermission } from '@backstage/plugin-permission-react';
    import { devToolsAdministerPermission } from '@backstage/plugin-devtools-common';
    
    export default function App() {
      return (
        <FlatRoutes>
          {/* other routes */}
          <Route
            path="/devtools"
            element={
              <RequirePermission permission={devToolsAdministerPermission}>
                <DevToolsPage />
              </RequirePermission>
            }
          >
            {customDevToolsPage}
          </Route>
        </FlatRoutes>
      );
    }

Optional permission policy on the backend

  1. Add the common package to the backend

    Copy
    yarn --cwd packages/backend add @backstage/plugin-devtools-common
  2. Use the permissions in your backend policy

    Copy
    // packages/backend/src/plugins/permission.ts
    import {
      devToolsAdministerPermission,
      devToolsConfigReadPermission,
      devToolsExternalDependenciesReadPermission,
      devToolsInfoReadPermission,
    } from '@backstage/plugin-devtools-common';
    import {
      AuthorizeResult,
      isPermission,
      PermissionPolicy,
      PolicyDecision,
      PolicyQuery,
    } from '@backstage/plugin-permission-node';
    
    class TestPermissionPolicy implements PermissionPolicy {
      async handle(request: PolicyQuery): Promise<PolicyDecision> {
        const user = request.identity;
    
        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 };
      }
    }

Optional config for the Info tab package list

Copy
# app-config.yaml
devTools:
  info:
    packagePrefixes:
      - '@roadiehq/backstage-'
      - '@spotify/backstage-'

Optional config for the External Dependencies tab

Copy
# app-config.yaml
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 you must ensure the host OS has a ping binary. For example in a Debian based image

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

Docker tip for Backstage version reporting

Copy the backstage.json file into the image so the Info tab can show the Backstage version.

Copy
WORKDIR /app
ENV NODE_ENV=production

COPY --chown=node:node . .
# ensure backstage.json ends up in the workdir at runtime
# if you build with a separate step copy it explicitly
# COPY --chown=node:node path/to/backstage.json ./

Changelog

This changelog is produced from commits made to the DevTools plugin since a year ago. 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