Module 05 · The frontend plugin · 40 min · You are: Platform team
A frontend plugin in the new frontend system
In this module
- You learn: What extensions, blueprints and frontend plugins are, how the app finds installed plugins, and how
app.extensionsconfigures any extension by id. - You do: Generate the plugin, write a placeholder page, find it in the sidebar and the Visualizer, then change its path in
app-config.yaml. - You end with: A Readiness item in the sidebar opens
/readiness, served by your own frontend plugin. Merged.
The new frontend system builds the whole app from extensions: pages, nav items, entity cards, API clients. You do not edit App.tsx to add a page; you install a package. Once you see the app as a tree of extensions with ids, you can configure, replace or turn off any part of it, yours or Backstage's.
Open this module in the interactive course to track progress.
Concepts
- Extension
- One piece of the app with an id (
page:readiness), a place it attaches to, and outputs such as a React element. Extensions. - Blueprint
- A factory for one kind of extension.
PageBlueprintmakes a page and, with atitle, a sidebar item. Blueprints, common blueprints. - createFrontendPlugin
- Groups extensions under a plugin id. The app installs every plugin package listed in
packages/app/package.json(app.packages: all). Frontend plugins. - Config by id
app.extensionsinapp-config.yamlcan disable an extension or set its config without code. The first course movedpage:catalogthis way.
Files to know
Open them in your editor or on GitHub; the bullets say what to look at.
portal/plugins/readiness/src/plugin.tsx(reference, your repo): The finished plugin: four extensions.portal/plugins/readiness/package.json(reference, your repo): A frontend plugin package.- The role and the plugin id L11–14
Steps
1. Generate the plugin
yarn new creates plugins/readiness and adds it to packages/app/package.json. That is all the app needs to install it.
cd ~/work/portal && yarn new --select frontend-plugin --option pluginId=readiness
grep -n readiness packages/app/package.json
cd plugins/readiness && rm -r src/components/TodoList src/components/TodoPage
2. Write the page and the plugin
A placeholder page for now. Module 06 fills it with data.
Create ~/work/portal/plugins/readiness/src/components/ReadinessPage.tsx
import { Container, Header, Text } from '@backstage/ui';
/** Every scorecard, lowest score first. For now: a placeholder. */
export const ReadinessPage = () => (
<>
<Header title="Production readiness" />
<Container>
<Text>Scorecards show up here once the page talks to the backend.</Text>
</Container>
</>
);
Replace the whole file ~/work/portal/plugins/readiness/src/plugin.tsx
The title makes a sidebar item.
import {
createFrontendPlugin,
PageBlueprint,
} from '@backstage/frontend-plugin-api';
import { rootRouteRef } from './routes';
/** /readiness: every scorecard. The title puts it in the sidebar. */
export const readinessPage = PageBlueprint.make({
params: {
path: '/readiness',
routeRef: rootRouteRef,
title: 'Readiness',
loader: () =>
import('./components/ReadinessPage').then(m => <m.ReadinessPage />),
},
});
export const readinessPlugin = createFrontendPlugin({
pluginId: 'readiness',
extensions: [readinessPage],
routes: {
root: rootRouteRef,
},
});
cd ~/work/portal && yarn tsc
3. Find it in the app
Restart the portal (Ctrl+C, then start it again): the app has a new package.
1. The sidebar has Readiness. Click it: /readiness shows Production readiness and the placeholder text.
2. Open Visualizer (bottom of the sidebar), tab Text, and search for readiness. You see page:readiness, attached to the app's routes.
4. Move it with config
Any extension's config can be set by its id. Try it, then take it out again.
Add to ~/work/portal/app-config.yaml under app: → extensions:, as the first item
- page:readiness:
config:
path: /scorecards
Save. The app reloads; Readiness in the sidebar now opens /scorecards. Remove it again:
Delete from ~/work/portal/app-config.yaml
- - page:readiness:
- config:
- path: /scorecards
5. Merge it
cd ~/work/portal
Propose it as a pull request. Put it on its own branch and push it:
git switch -c readiness-frontend && git add -A && git commit -qm "Readiness: frontend plugin with a page"
git push -qu origin HEAD
Open the pull request. It prints a link:
gh pr create --fill
Open that link. Files changed shows exactly your diff.
Merge it on GitHub (Squash and merge, then Delete branch), or from the terminal:
gh pr merge --squash --delete-branch
Bring your copy up to date:
git switch -q main && git pull -q
Simplified for the course
The page has no data yet. Module 06 adds the API client.
Break it on purpose: Configure an extension that does not exist
The platform team moves the page to /scorecards for a demo, and types the id from memory.
You practise: where the new frontend system reports config mistakes.
1. Break it
Add to ~/work/portal/app-config.yaml under app: → extensions:, as the first item
One letter short.
- page:readines:
config:
path: /scorecards
Save, then open http://localhost:3000/scorecards.
2. What you see
Expected: the Readiness page at /scorecards. You get: a not-found page, and Readiness still opens /readiness. No error on the page.
3. Fix it yourself
Find where the app reports the mistake, then fix the id. You are done when /scorecards shows the page.
Hint 1
Open the browser's developer tools, tab Console, and reload.
Hint 2
Compare the id with the one in the Visualizer.
The fix
The console says App startup encountered warnings: and INVALID_EXTENSION_CONFIG_KEY: Extension page:readines does not exist.
Change in ~/work/portal/app-config.yaml
- - page:readines:
+ - page:readiness:
Check /scorecards, then remove the three lines again: the course keeps /readiness.
After you fixed it: the five questions
- What failed?
- The config named an extension id that no plugin provides, so the app ignored it.
- How was it detected?
- Only as a warning in the browser console.
- Who responds?
- The platform team, who own
app-config.yaml. - How is it corrected?
- Use the exact id shown in the Visualizer.
- How is it prevented?
- Copy ids from the Visualizer, and open the browser console in every review of app config changes.
Quiz
- How did the app learn about your plugin?
yarn newadded the package topackages/app/package.json, and the app installs every plugin package listed there- You imported the plugin in
App.tsxand added it to the list of routes - The backend sends the list of plugins to the browser at startup
- What makes the Readiness item appear in the sidebar?
- A
NavItemcomponent added toSidebar.tsxbyyarn new - The
titleon the page made withPageBlueprint - A
sidebar:key under the plugin inapp-config.yaml
- A
- How do you change the path of someone else's page without forking it?
- Copy the plugin into your repo and change the path there
- Wrap the page in your own page with a different route
- Set
config.pathfor its extension id underapp.extensions
Answers
yarn newadded the package topackages/app/package.json, and the app installs every plugin package listed there Withapp.packages: all, a dependency is all it takes.- The
titleon the page made withPageBlueprintA page with a title also gives the app a nav item. - Set
config.pathfor its extension id underapp.extensionsEvery extension is configurable by id; that is the point of the system.
Field note
The Visualizer is the most underused page in Backstage. When a page is missing or in the wrong place, I start there: the extension is either not in the tree, or attached somewhere I did not expect.