04 - Customizing Backstage¶
Domain 4: Customizing Backstage (32%)
Curriculum topics: understand frontend versus backend plugins; customizing Backstage plugins; make changes to React code in the Backstage App; using Material UI components.
The largest domain. YAML and TypeScript here are quoted or adapted from the docs and were not run or compiled; the TypeScript snippets are shown to be read, not copied blind.
Two frontend systems. The Backstage docs describe a new frontend system (plugins are discovered and wired through extensions and blueprints) and a legacy one (routes and sidebar written by hand in
App.tsxandRoot.tsx). Several pages are marked legacy and point you to the new docs. Expect to recognize both. Check which one the exam targets.
Frontend versus backend plugins¶
| Frontend plugin | Backend plugin | |
|---|---|---|
| Runs in | The browser, inside the app | The Backstage backend process |
| Package location | Added to packages/app | Added to packages/backend |
| Typical job | Pages, cards, tabs, entity content | APIs, data fetching, integrations, scheduled work |
| Talks to others via | Extensions, Utility APIs and routes | Extension points and core services |
| Install command | yarn --cwd packages/app add <package> | yarn --cwd packages/backend add <package> |
The install guide says frontend UI plugins go in the app package and backend plugins go in backend.
Both sides keep plugins isolated. "There can be no direct communication between plugins through code," so a backend plugin is effectively its own microservice, and a frontend plugin calls its backend over an API.
Backend modules extend another plugin without forking it. A module attaches to an extension point of one plugin and must deploy with that plugin. The catalog is the classic case: a GitHub discovery module is a module of the catalog plugin.
π Architecture overview - plugin forms, libraries, modules
Customizing plugins¶
Creating one¶
yarn new is the scaffolder. The docs name the backend-plugin and backend-module choices; for a frontend plugin pick the matching entry in the prompt (the frontend guide says only "plugin").
- A new frontend plugin lands in
plugins/<pluginId>(frontend guide). The-backendand-backend-module-directory suffixes are the usual convention and were not confirmed on a fetched page. - Serve one plugin in isolation with
yarn startinside its directory (faster startup and hot reload, for local development only). The frontend guide gives/exampleas the default page path and does not name a port; the plugin's own dev server prints its URL.
A frontend plugin (new frontend system)¶
import { createFrontendPlugin } from '@backstage/frontend-plugin-api';
export const examplePlugin = createFrontendPlugin({
pluginId: 'example',
extensions: [],
});
- The plugin ID is "a lowercase dash-separated string"; the exported instance is conventionally the camel-cased ID plus
Plugin(a convention, not stated on the page). src/index.tsdefault-exports the plugin so integrators can install it without referencing it in code.- Extensions come from blueprints.
PageBlueprintcreates a standalone page; itsparamsincluderouteRef,path,title,iconand a lazyloader. The title and icon populate the sidebar automatically. - Route references use
createRouteRef()and are passed to the plugin'sroutesso integrators can bind external links. - Other blueprints in the docs:
ApiBlueprint(Utility APIs) andEntityContentBlueprint(a tab on an entity page).
Installing a frontend plugin¶
yarn --cwd packages/app add @backstage-community/plugin-tech-radar
With the new system, plugins are discovered automatically when app.packages is all:
app:
packages: all
Extension configuration can be set in app-config.yaml:
app:
extensions:
- page:tech-radar:
config:
path: /tech-radar
Manual installation passes the plugin as a feature to createApp from @backstage/frontend-defaults:
import { createApp } from '@backstage/frontend-defaults';
import techRadarPlugin from '@backstage-community/plugin-tech-radar/alpha';
const app = createApp({
features: [techRadarPlugin],
});
export default app.createRoot();
In the new system, plugins that provide a page register their sidebar item automatically. To reorder, group or add entries you override the built-in app/nav extension.
A backend plugin and a module¶
import {
createBackendPlugin,
coreServices,
} from '@backstage/backend-plugin-api';
export const examplePlugin = createBackendPlugin({
pluginId: 'example',
register(env) {
env.registerInit({
deps: {
logger: coreServices.logger,
httpRouter: coreServices.httpRouter,
},
async init({ logger, httpRouter }) {
logger.info('Hello from example plugin');
// httpRouter.use(router) mounts the plugin's routes
},
});
},
});
createBackendPlugintakes apluginIdand aregister(env)function. Inside it,env.registerInitdeclaresdeps(core services or extension points) and aninitfunction that receives them.coreServices.loggerandcoreServices.httpRouterare plugin-scoped: the logger may tag messages with the plugin ID.- Read configuration by depending on
coreServices.rootConfigand calling methods such asconfig.getOptionalString('example.value'). - A module uses
createBackendModulewith the targetpluginIdand its ownmoduleId, and should depend on the target's library package (such as@backstage/plugin-catalog-node) and not on the plugin package. - Module rules: each module package contains a single module; modules deploy in the same backend as the plugin they extend; register HTTP handlers under
/modules/<module-id>. - A plugin exposes its own extension point with
createExtensionPointandenv.registerExtensionPoint.
The docs' backend page does not cover installing with backend.add; the catalog GitHub discovery docs (note 03) show the pattern: backend.add(import('@backstage/plugin-catalog-backend-module-github'));.
π Building frontend plugins - createFrontendPlugin, blueprints
π Building backend plugins and modules - createBackendPlugin, modules, extension points
π Configure your app with plugins - install and discovery
React code in the Backstage app¶
The app package (packages/app) is an ordinary React and TypeScript application. Your changes go there:
- Entry and wiring:
packages/app/src/App.tsxcreates the app. In the new system that iscreateAppfrom@backstage/frontend-defaultswithfeatures; in the legacy systemcreateAppfrom the app API takesapis,componentsandthemes. - Themes: register them in
createApp. The docs warn that your list overrides the default themes, so provide bothlightanddark:
const app = createApp({
apis,
components,
themes: [
{
id: 'light',
title: 'Light theme',
variant: 'light',
icon: <LightIcon />,
Provider: ({ children }) => (
<UnifiedThemeProvider theme={lightTheme} children={children} />
),
},
// dark theme entry follows the same shape
],
});
- Plugin pages and routes: in the legacy system these are
<Route>elements inApp.tsxand sidebar entries inRoot.tsx; the docs mark that guide as legacy. In the new system, a plugin'sPageBlueprintsupplies both. - Entity pages: extra tabs are contributed with
EntityContentBlueprintin the new system. - Per the architecture page, extensions are the building blocks of the app's visual and non-visual structure, arranged in an app extension tree; extension overrides are high-priority extensions that can replace existing ones; routes let plugins link to each other without knowing URL paths.
Material UI components and theming¶
Backstage's UI is built on Material UI (MUI) and is moving toward Backstage UI (BUI). The customization page says both exist: the legacy system uses MUI with JS-based theming, and Backstage UI "uses CSS variables and tokens." Inspect a component's class names: a bui- prefix means you style it with BUI tokens.
MUI theme with createUnifiedTheme¶
import {
createBaseThemeOptions,
createUnifiedTheme,
palettes,
} from '@backstage/theme';
export const lightTheme = createUnifiedTheme({
...createBaseThemeOptions({
palette: palettes.light,
}),
fontFamily: 'Comic Sans MS',
defaultPageTheme: 'home',
});
- Override palette colors by spreading
palettes.lightand replacing keys such asprimary,secondary,backgroundandnavigation(background, indicator, color, selectedColor). - Typography is overridden inside
createBaseThemeOptions; spreaddefaultTypographyto change only some settings. - Custom fonts are declared as
@font-faceentries undercomponents.MuiCssBaseline.styleOverrides. - Component overrides target Backstage components by name, for example
BackstageHeaderwithstyleOverrides. - If MUI v5 class names are missing the
v5-prefix, configureClassNameGeneratorbefore any MUI components load.
Backstage UI tokens¶
Create a CSS file, import it in App.tsx, and target modes with data-theme-mode:
[data-theme-mode='light'] {
--bui-bg-app: #f8f8f8;
--bui-fg-primary: #000;
}
[data-theme-mode='dark'] {
--bui-bg-app: #333333;
--bui-fg-primary: #fff;
}
Key tokens: --bui-bg-app, --bui-bg-neutral-1, --bui-bg-solid, --bui-fg-primary, --bui-fg-solid, --bui-border-2, --bui-font-regular, --bui-space (default 0.25rem) and --bui-radius-1 to --bui-radius-6. The @backstage/plugin-mui-to-bui plugin generates BUI variables from your MUI themes; the docs call its output best-effort, so review it.
π User interface customization - MUI themes, BUI tokens, overrides
Key terms¶
- Frontend plugin - a package that adds UI features to the Backstage app.
- Backend plugin - a package that adds server-side features to the Backstage backend.
- Backend module - a package that extends a single plugin through its extension points.
- Extension - a building block of the app's visual or non-visual structure in the new frontend system.
- Blueprint - a helper such as
PageBlueprintthat creates an extension of a given kind. - Utility API - an interface frontend plugins use to share functionality or let integrators customize behavior.
- Route ref - a handle for a page route that lets plugins link without hard-coded URLs.
- Core service - a backend utility such as the logger, HTTP router or root config.
- registerInit - the call where a backend plugin declares its dependencies and init function.
- Material UI (MUI) - the React component library behind Backstage's legacy UI and themes.
- createUnifiedTheme - the
@backstage/themefunction that builds a Backstage theme for MUI. - Backstage UI (BUI) - the newer component system styled with CSS variables and tokens.