Domain 1: Backstage Development Workflow
- A Backstage app is created with the official scaffolding command, npx @backstage/create-app, which generates a monorepo. That repository is your app - Backstage is a framework you build on rather than a product you install and configure.
- The generated repository is a Yarn workspaces monorepo with packages/app for the frontend and packages/backend for the backend, plus a plugins directory for anything you write yourself. Knowing which package a change belongs in is most of the workflow.
- yarn install resolves and installs dependencies across every workspace at once, which is why installing inside an individual package rather than at the root causes duplicate and mismatched dependency versions.
- yarn dev runs the frontend and backend together for local development, with the frontend on port 3000 and the backend on port 7007 by default. Running only one of the two is the usual reason the UI loads but every API call fails.
- Backstage is written in TypeScript, and yarn tsc type-checks the whole workspace. A type error anywhere in the monorepo will fail the build even if the package you are working on is fine.
- yarn build produces the production bundles - the frontend as static assets and the backend as a compiled bundle - which is a different output from the development server and the step that catches problems the dev server tolerates.
- Node version matters. Backstage supports specific Node versions and the engines field enforces them, so a mismatched Node install is a common cause of an install that fails before any code runs.
- yarn lint and yarn test run ESLint and Jest across the workspace, and both are wired into the generated repository from the start. Keeping them passing is what makes upgrades survivable.
- The Backstage CLI wraps the build tooling so plugins and packages share one configuration. You invoke it through the package scripts rather than configuring webpack, Jest or ESLint yourself.
- Backstage releases move quickly and are coordinated as versioned release lines. yarn backstage-cli versions:bump upgrades the Backstage packages together, which is far safer than upgrading individual packages by hand.
- The production container is built with the multi-stage Dockerfile in the generated repository, which builds the backend bundle and produces an image serving both the compiled frontend assets and the backend.
- The standard Backstage image is a host build: dependencies are installed and the bundle produced on the build host, then copied into the image. That is why the Dockerfile expects yarn build to have run first in the documented flow.
- Local development uses an in-memory or SQLite database by default so nothing external is needed, while production expects PostgreSQL. Software that works locally and fails in production often depends on that difference.
- Create a new plugin with yarn new, which scaffolds a frontend or backend plugin into the plugins directory and wires it into the workspace. Writing the directory by hand misses the registration the command performs.
Domain 2: Backstage Infrastructure
- Backstage is a framework for building an internal developer portal, maintained by the CNCF and originally from Spotify. It gives you a core plus a plugin architecture; the portal your organisation runs is an application you assemble and maintain.
- The architecture is client-server: a React single-page application frontend and a Node.js backend. The frontend renders the UI and calls backend APIs; the backend holds the catalog, the plugin backends, authentication and the database connection.
- The backend is composed of backend plugins and the modules that extend them, wired together by the backend system in packages/backend. Adding a backend plugin means installing it and adding it to that composition.
- Configuration lives in YAML files loaded in order: app-config.yaml as the base, then app-config.local.yaml for local overrides that are not committed, then app-config.production.yaml in production. Later files override earlier ones.
- Never commit secrets to app-config.yaml. Use environment variable substitution with the ${ENV_VAR} syntax so tokens and connection strings come from the environment, which is also what makes the same config work across environments.
- app.baseUrl and backend.baseUrl must be correct for the environment, because the frontend uses them to reach the backend and the backend uses them to build links. Most "works locally, broken in production" problems start here.
- backend.cors must list the frontend origin in production, or the browser blocks every API call while the backend itself looks healthy - which is why the symptom appears to be a frontend fault.
- Production requires PostgreSQL, configured under backend.database. SQLite is for local development only, and using it in production loses data on restart and cannot support more than one backend instance.
- Authentication is configured with auth providers such as GitHub, Google, Microsoft or OIDC, and sign-in resolvers map the external identity to a Backstage user entity in the catalog. A user who authenticates but has no matching entity cannot sign in.
- Integrations configure access to source-control hosts - GitHub, GitLab, Bitbucket, Azure - with tokens or app credentials. The catalog and scaffolder both rely on them, so a missing integration shows up as failures to read or create repositories.
- Deploy Backstage as a container behind a reverse proxy or ingress, with the database external and the configuration and secrets injected as environment variables. Helm charts and the Backstage Kubernetes deployment pattern are the common route.
- Backstage keeps state in PostgreSQL rather than in memory, so the container is stateless and can be replaced or scaled. Anything written to the container filesystem is lost, which matters for techdocs unless external storage is configured.
- Plan for upgrades as part of running Backstage. Because the portal is your codebase, staying close to current releases is far cheaper than a large jump later, and the versions:bump workflow exists for exactly that.
Domain 3: Backstage Catalog
- The Software Catalog is the heart of Backstage: a metadata store of the software in your organisation - services, websites, libraries, APIs - and the teams and systems around them. It answers who owns this, what does it depend on, and where do I find it.
- Entities are described in YAML files, conventionally catalog-info.yaml, kept in the same repository as the code they describe. Keeping metadata with the code is what stops it going stale.
- Every entity has apiVersion, kind, metadata and spec. metadata.name identifies it within its kind and namespace, and the combination of kind, namespace and name is the entity reference used everywhere else.
- Know the core kinds: Component for software, API for an interface a component exposes, Resource for infrastructure, System and Domain for grouping, Group and User for people, and Location for pointers to more entity files.
- A Component declares spec.type such as service, website or library, spec.lifecycle such as experimental, production or deprecated, and spec.owner. Owner is required, and it must reference a Group or User that exists in the catalog.
- Relationships between entities come from spec fields: dependsOn, providesApis, consumesApis, partOf and subcomponentOf. The catalog turns these into the dependency graph and the system diagram rather than you drawing anything.
- Annotations under metadata.annotations connect an entity to external systems, and plugins read them. backstage.io/techdocs-ref points at documentation, and plugins such as Kubernetes, PagerDuty and CI integrations each look for their own annotation.
- A plugin that shows nothing on an entity page is usually missing its annotation rather than misconfigured. The annotation is the link between the entity and the external system the plugin queries.
- Labels are for selecting and filtering entities, tags are free-form keywords for search and discovery, and annotations carry machine-readable references for tooling. They are not interchangeable.
- Manual registration adds a single catalog-info.yaml by URL through the Register Existing Component flow or by listing it under catalog.locations in app-config, which creates a Location entity that the catalog polls for changes.
- A single YAML file can hold several entities separated by ---, which is the normal way a repository declares a component, the API it provides and the resources it uses together.
- Automated ingestion uses discovery processors that scan an organisation for catalog files - GitHub discovery walking repositories, or LDAP and organisational data providers importing users and groups. That is what keeps a large catalog current without manual registration.
- The catalog processes entities on a loop: read the location, process and validate the entity, then stitch relationships. Because it is periodic, a change to a YAML file appears after the next refresh rather than instantly.
- Troubleshoot ingestion from the entity's own page, which shows processing errors, and from the backend logs. The usual causes are invalid YAML, a missing required field, an owner that does not exist, or a duplicate name within a kind and namespace.
- A missing owner is the most common validation failure, because the referenced Group must itself be in the catalog. Importing your organisation's groups and users is normally a prerequisite for importing components.
- Entities can be marked with a lifecycle of deprecated and removed by deleting the file or the location. Deleting an entity from the UI without removing its source only postpones it, since the next refresh re-ingests it.
Domain 4: Customizing Backstage
- Backstage is customised by writing code in your own repository, not by editing settings in a running product. Every customisation - a plugin, a page, a tab, a theme - is a change to the app you maintain.
- Frontend plugins are React packages that add pages, entity tabs and cards to the UI. Backend plugins are Node packages that add routes, integrate with external systems and can own database tables.
- Many capabilities need both halves: the frontend plugin renders, and the backend plugin holds the credentials and calls the external API. Putting a secret in the frontend exposes it to every browser, which is why the split exists.
- Install a frontend plugin by adding the package to packages/app and wiring it in - a route in App.tsx, an item in the sidebar, or a tab or card in the entity page - because installing the dependency alone renders nothing.
- packages/app/src/App.tsx defines the routes and the app's composition, and components/catalog/EntityPage.tsx defines what appears on an entity's page. Those two files are where most frontend customisation lands.
- The entity page uses switch components such as EntitySwitch to show different content by entity kind or type, so a service and a website can present different tabs from one definition.
- Conditional content is driven by the entity's own data, commonly with isKind, isComponentType or an annotation predicate, so a plugin tab appears only for entities that have the annotation it needs rather than showing an error everywhere else.
- Sidebar navigation is defined in Root.tsx, where you add a SidebarItem with an icon and the route path. A plugin route that works when typed into the address bar but has no menu entry is simply missing this.
- Backstage's UI is built on Material UI, so customisation uses MUI components and conventions - Grid for layout, Card for panels, Typography for text - alongside Backstage's own components such as Page, Header, Content and InfoCard.
- Use the Backstage component library before reaching for raw MUI, because Page, Header, Content, InfoCard, Table and the progress and error components already handle the loading, empty and error states consistently with the rest of the portal.
- Theming is done by creating a theme from Backstage's theme helpers and supplying it in the app definition, which is how you apply organisational colours and typography without editing every component.
- A frontend plugin is defined with createPlugin and exposes routable extensions created with createRoutableExtension for pages and createComponentExtension for cards, so the app can lazily load them.
- Plugins call backends through the API system rather than fetching directly: obtain an ApiRef such as fetchApi or discoveryApi through useApi, which handles the backend URL and authentication consistently.
- discoveryApi resolves the base URL of a backend plugin so a frontend plugin never hard-codes it, which is what lets the same code work locally on port 7007 and behind a production ingress.
- The Scaffolder is the software templates plugin, defined by Template entities that describe input parameters and a sequence of steps such as fetching a skeleton, publishing a repository and registering the result in the catalog. Custom actions extend it.
- TechDocs builds documentation from Markdown alongside the code using MkDocs, and is connected to an entity by the backstage.io/techdocs-ref annotation. In production, generated docs are stored in external object storage rather than in the container.
- Search is itself a plugin with pluggable collators that index the catalog, TechDocs and other sources, so adding a new searchable source means adding a collator rather than changing search itself.
- Prefer configuration and existing plugins to forking. Because your Backstage app is a codebase you own, every unnecessary local modification is something you carry through every future upgrade.
CBA exam tips
- Customizing Backstage is the largest domain at 32%. Know the frontend against backend plugin split, where App.tsx, EntityPage.tsx and Root.tsx each fit, and that Backstage's UI is built on Material UI.
- The single most important idea on this exam: Backstage is a framework you build an application with, not a product you install and configure. Almost every customisation answer involves writing or wiring code in your own repository.
- Learn the catalog entity model precisely - apiVersion, kind, metadata, spec - and the core kinds Component, API, Resource, System, Domain, Group, User and Location. Owner is required and must reference a Group or User that already exists.
- Distinguish labels, tags and annotations. Annotations are the machine-readable links plugins read, which is why a plugin showing nothing on an entity page is usually a missing annotation rather than a broken plugin.
- Memorise the local development commands and their ports: yarn install at the root, yarn dev for frontend on 3000 and backend on 7007, plus yarn tsc, yarn lint, yarn test and yarn build.
- For production questions, the recurring facts are PostgreSQL rather than SQLite, correct app.baseUrl and backend.baseUrl, backend.cors listing the frontend origin, and secrets supplied through ${ENV_VAR} substitution rather than committed to app-config.yaml.
- Configuration files load in order and override each other: app-config.yaml, then app-config.local.yaml locally, then app-config.production.yaml. Expect a question that turns on which value wins.
- For ingestion troubleshooting, the catalog processes on a periodic loop, errors surface on the entity page and in backend logs, and the usual causes are invalid YAML, a missing owner, or a duplicate name within a kind and namespace.
Study guide FAQ
What is the format of the CBA exam?
It is a 90-minute online proctored multiple-choice exam costing US$250, or US$495 bundled with an annual subscription. Unlike the Kubernetes administrator exams it is not a hands-on performance test, so you answer questions about Backstage rather than building a portal live.
Which domain carries the most weight?
Customizing Backstage at 32%, followed by Backstage Development Workflow at 24%, with Backstage Infrastructure and Backstage Catalog at 22% each. Customisation and the development workflow together are over half the exam.
Do I need to know React and TypeScript?
Yes, at a working level. Backstage is written in TypeScript and its frontend is React, and the published objectives explicitly include making changes to React code in the Backstage app and using Material UI components. You are not asked to write a complex application, but you should be comfortable reading a React component, understanding how a route and a sidebar item are wired, and recognising valid TypeScript.
What is the difference between a frontend and a backend plugin?
A frontend plugin is a React package that adds pages, entity tabs or cards to the UI and is wired into packages/app. A backend plugin is a Node package that adds API routes, talks to external systems and can own database tables, wired into packages/backend. Many integrations need both halves, because the backend is where credentials belong - anything a frontend plugin holds is visible to every browser.
Why is my entity not appearing in the catalog?
Check in this order. Is the location registered, either manually or through a discovery provider? Is the YAML valid and does it have apiVersion, kind, metadata and spec? Does metadata.name collide with another entity of the same kind and namespace? Does spec.owner reference a Group or User that actually exists in the catalog, which is the most common failure? Then remember the catalog refreshes on a loop, so a change appears at the next refresh rather than immediately. Processing errors are shown on the entity page and in the backend logs.