Backstage Development Workflow
The Backstage Development Workflow domain tests how you scaffold, run, test, and extend a Backstage app with the official CLI and package conventions. Expect questions on app packages versus plugins, the local dev loop, type checking and tests, and a plugin's public API surface.
App and plugin structure
A new app comes from npx @backstage/create-app@latest. The frontend lives in packages/app; the backend lives in packages/backend; reusable frontend and backend plugins live under plugins/. The app composes plugins through packages/app/src/App.tsx, and the backend registers plugins in packages/backend/src/index.ts. Frontend plugins export a plugin object from src/plugin.ts using createPlugin and expose route components with createRoutableExtension. Backend plugins use createBackendPlugin and coreServices.
Local workflow
yarn dev runs the frontend and backend together; by default the frontend is on port 3000 and the backend on 7007, with the frontend proxying /api to the backend. yarn tsc type-checks the workspace, yarn test runs Jest tests through the Backstage CLI, and yarn build produces production bundles. yarn new scaffolds a plugin, backend plugin, or other package. Plugins keep a generated api-report.md that records the public API; do not edit it by hand-regenerate it with the API report command. Backstage uses changesets for package versioning, so user-facing plugin changes normally include a changeset.
Worked example
You need a frontend page at /my-plugin. Run yarn new, select the frontend plugin option, and use plugin ID my-plugin. The generator creates plugins/my-plugin/src/plugin.ts with createPlugin({ id: 'my-plugin', routes: { root: rootRouteRef } }) and a routable extension. Export the routable extension from the plugin's src/index.ts. In packages/app/src/App.tsx, import the extension as myPlugin and add <Route path="/my-plugin" element={<myPlugin />} /> inside the app's FlatRoutes. Run yarn tsc and yarn dev. Reasoning: the plugin ID, route path, and exported extension are separate contracts; the app consumes the routable extension, not the raw page component, so the route ref is wired correctly.
Traps
Do not import from legacy @backstage/core; use @backstage/core-components and @backstage/core-plugin-api. Do not confuse yarn start (frontend package start) with yarn dev (full local stack). Do not forget to add the plugin as a dependency of packages/app; generated apps do this, but manual plugins often miss it. A plugin ID mismatch between createPlugin and the route is a common failure.