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
-
Add the backend package
yarn --cwd packages/backend add @backstage/plugin-devtools-backend
-
Register the backend plugin
Open packages/backend/src/index.ts
Add the module to your backend
// inside createBackend setup backend.add(import('@backstage/plugin-devtools-backend'));
-
Start your backend
yarn --cwd packages/backend start
Install the backend with the classic backend system
-
Add the backend package
yarn --cwd packages/backend add @backstage/plugin-devtools-backend
-
Create the router
Create a new file at packages/backend/src/plugins/devtools.ts
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, }); }
-
Mount the router
Open packages/backend/src/index.ts
Add the plugin env and mount the route
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));
-
Start your backend
yarn --cwd packages/backend start
Install the frontend
-
Add the frontend package
yarn --cwd packages/app add @backstage/plugin-devtools
-
Add the route
Open packages/app/src/App.tsx
Import the page
import { DevToolsPage } from '@backstage/plugin-devtools';
Add the route before the closing FlatRoutes tag
<Route path="/devtools" element={<DevToolsPage />} />
-
Add the sidebar link
Open packages/app/src/components/Root/Root.tsx
Import the icon
import BuildIcon from '@material-ui/icons/Build';
Add the sidebar item after SidebarSettings
<SidebarItem icon={BuildIcon} to="devtools" text="DevTools" />
-
Start your app
yarn start
Optional tabs in the frontend
Add External Dependencies
-
Create a custom page file
Create 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 />;
-
Use the custom page in routing
Open packages/app/src/App.tsx
Import the custom page
import { customDevToolsPage } from './components/devtools/CustomDevToolsPage';
Wrap the route
<Route path="/devtools" element={<DevToolsPage />}> {customDevToolsPage} </Route>
-
Start your app
yarn start
Add Catalog Unprocessed Entities tab
-
Install the plugin as per its documentation
-
Import its content into your custom page
import { UnprocessedEntitiesContent } from '@backstage/plugin-catalog-unprocessed-entities';
-
Add the route to your custom layout
<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
-
Add the common package
yarn --cwd packages/app add @backstage/plugin-devtools-common
-
Update the sidebar
Open packages/app/src/components/Root/Root.tsx
Import the helpers
import { devToolsAdministerPermission } from '@backstage/plugin-devtools-common'; import { RequirePermission } from '@backstage/plugin-permission-react';
Wrap the sidebar item
<RequirePermission permission={devToolsAdministerPermission} errorPage={<></>}> <SidebarItem icon={BuildIcon} to="devtools" text="DevTools" /> </RequirePermission>
Secure the route
-
Add the common package if not already added
yarn --cwd packages/app add @backstage/plugin-devtools-common
-
Wrap the route
Open packages/app/src/App.tsx
Import the permission
import { devToolsAdministerPermission } from '@backstage/plugin-devtools-common'; import { RequirePermission } from '@backstage/plugin-permission-react';
Wrap the route element
<Route path="/devtools" element={ <RequirePermission permission={devToolsAdministerPermission}> <DevToolsPage /> </RequirePermission> } />
-
If you use a customDevToolsPage then wrap the element and keep the children
<Route path="/devtools" element={ <RequirePermission permission={devToolsAdministerPermission}> <DevToolsPage /> </RequirePermission> } > {customDevToolsPage} </Route>
Example backend permission policy
-
Add the common package to the backend
yarn --cwd packages/backend add @backstage/plugin-devtools-common
-
Import the permissions where you define your policy
import { devToolsAdministerPermission, devToolsConfigReadPermission, devToolsExternalDependenciesReadPermission, devToolsInfoReadPermission, } from '@backstage/plugin-devtools-common';
-
Sample policy code
// 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
devTools:
info:
packagePrefixes:
- '@roadiehq/backstage-'
- '@spotify/backstage-'
Configure External Dependencies
Add endpoints to your app config
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
RUN \
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
WORKDIR /app
ENV NODE_ENV=production
# include backstage.json in the image
COPY backstage.json ./
Summary of imports to show the UI
-
In App.tsx import and route
import { DevToolsPage } from '@backstage/plugin-devtools'; <Route path="/devtools" element={<DevToolsPage />} />
-
In Root.tsx add the sidebar link
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
Focus on using Backstage, rather than building and maintaining it.