Scaffold and implement a frontend dynamic plugin for Red Hat Developer Hub (RHDH).
- New pages and routes
- Entity page cards and tabs
- Sidebar menu items
- Custom themes
- Scaffolder field extensions
- TechDocs addons
- Search result types and filters
- Any UI component for RHDH
Not for backend plugins — use the backend command instead.
- Determine RHDH version
- Scaffold app and plugin
- Configure RHDH themes (optional)
- Implement plugin components
- Export and package (see
exportcommand) - Configure plugin wiring (see
wiringcommand)
Consult ../../rhdh/references/versions.md for the compatibility matrix. Ask the user which RHDH version they target if not specified.
Run the scaffold script:
python scripts/scaffold.py \
--type frontend \
--rhdh-version 1.9 \
--plugin-id my-pluginAdd --with-theme to install the RHDH theme package. Run python scripts/scaffold.py --help for all options.
Generated structure:
plugins/<plugin-id>/
├── src/
│ ├── index.ts # Public exports
│ ├── plugin.ts # Plugin definition
│ ├── routes.ts # Route references
│ └── components/
│ └── ExampleComponent/
├── package.json
└── dev/
└── index.tsx # Development harness
cd plugins/<plugin-id>
yarn add @red-hat-developer-hub/backstage-plugin-themeUpdate dev/index.tsx:
import { getAllThemes } from '@red-hat-developer-hub/backstage-plugin-theme';
import { createDevApp } from '@backstage/dev-utils';
import { myPlugin, MyPage } from '../src';
createDevApp()
.registerPlugin(myPlugin)
.addPage({
element: <MyPage />,
title: 'My Plugin',
path: '/my-plugin',
})
.addThemes(getAllThemes())
.render();getThemes()/useThemes()— Latest RHDH light and dark themesgetAllThemes()/useAllThemes()— All themes including legacy versionsuseLoaderTheme()— Returns Material-UI v5 theme object
When deployed to RHDH, the application shell provides theming automatically. This configuration is only for local development.
// src/components/MyPage/MyPage.tsx
import React from 'react';
import { Page, Header, Content } from '@backstage/core-components';
export const MyPage = () => (
<Page themeId="tool">
<Header title="My Plugin" />
<Content>
<h1>Hello from My Plugin</h1>
</Content>
</Page>
);// src/components/MyCard/MyCard.tsx
import React from 'react';
import { InfoCard } from '@backstage/core-components';
import { useEntity } from '@backstage/plugin-catalog-react';
export const MyEntityCard = () => {
const { entity } = useEntity();
return (
<InfoCard title="My Plugin Info">
<p>Entity: {entity.metadata.name}</p>
</InfoCard>
);
};// src/index.ts
export { myPlugin } from './plugin';
export { MyPage } from './components/MyPage';
export { MyEntityCard } from './components/MyCard';Build and verify:
cd plugins/<plugin-id>
yarn buildUse the export command for the full pipeline. Quick version:
cd plugins/<plugin-id>
npx @red-hat-developer-hub/cli@latest plugin export
npx @red-hat-developer-hub/cli@latest plugin package \
--tag quay.io/<namespace>/<plugin-name>:v0.1.0
podman push quay.io/<namespace>/<plugin-name>:v0.1.0For advanced options, use the export command reference.
Use the wiring command to auto-generate configuration from plugin source. Or configure manually:
plugins:
- package: oci://quay.io/<namespace>/<plugin-name>:v0.1.0!my-plugin
disabled: false
pluginConfig:
dynamicPlugins:
frontend:
my-org.plugin-my-plugin:
dynamicRoutes:
- path: /my-plugin
importName: MyPage
menuItem:
icon: dashboard
text: My Plugin| Option | Purpose |
|---|---|
dynamicRoutes |
Full page routes with optional sidebar menu items |
mountPoints |
Entity page cards, tabs, and other integrations |
menuItems |
Sidebar ordering and nesting |
appIcons |
Custom icons for routes and menus |
entityTabs |
New tabs on entity pages |
For complete wiring options, read frontend-wiring.md (in this directory).
Add class name generator to src/index.ts:
import { unstable_ClassNameGenerator as ClassNameGenerator } from '@mui/material/className';
ClassNameGenerator.configure(componentName =>
componentName.startsWith('v5-') ? componentName : `v5-${componentName}`
);
export * from './plugin';Apply spacing manually to MUI v5 Grid:
<Grid container spacing={2}>
<Grid item>...</Grid>
</Grid>The scalprum.name in package.json (auto-generated during export) must match the key under dynamicPlugins.frontend.<key> in dynamic-plugins.yaml. Default derivation: @my-org/backstage-plugin-foo → my-org.backstage-plugin-foo.
Plugins using the new Backstage frontend system (createPlugin from @backstage/frontend-plugin-api) have different export patterns than the legacy system (createPlugin from @backstage/core-plugin-api). Check which system the plugin uses.