Installing into an existing Backstage app
This guide walks through installing the OpenChoreo plugin set into a Backstage app you already own.
Two install paths are supported:
- New Frontend System (NFS) β the default scaffold from
npx @backstage/create-app@latest. Plugins are added as features tocreateApp({ features: [...] }), and every OpenChoreo entity tab and overview card auto-mounts. NoEntityPage.tsxedits. This is the path you should follow unless you have a reason not to. - Legacy frontend system β for hosts scaffolded with
--legacy, or older apps where you have heavyEntityPage.tsxcustomization you want to keep. See Section 9 β Legacy frontend system (fallback).
The guide is organized by feature. Sections 1β3 are setup. Section 4 (Core) is the only mandatory install β after finishing it you have a working OpenChoreo-aware Backstage with Domain/System/Component pages and the Cell, Deploy, and Definition tabs rendering real data. Sections 5β7 each add one optional tab pack on top of Core; you can install any subset independently.
OpenChoreo plugins are published to the public npm registry under the @openchoreo scope. You install them with yarn add β no registry configuration and no authentication required.
The install commands on this page reference @openchoreo/<pkg>@^1.2.0, which will be the GA dist-tag of the next plugin release. While 1.2.0 is still under active development, install via the next dist-tag to get the latest prerelease today:
yarn workspace app add @openchoreo/backstage-plugin@next
Once 1.2.0 GA is announced, swap @next for @^1.2.0 to pin to the stable release.
1. Prerequisitesβ
- A Backstage workspace on the supported Backstage version. This guide pins to Backstage
1.51.0. - The workspace must be scaffolded with the default NFS scaffold:
npx @backstage/create-app@latest. (Do NOT pass--legacy. If you must stay on legacy, see Section 9.) - Node.js 20 or 22, Yarn 4.13.x (the current
create-appscaffold ships Yarn 4.13.0 in.yarn/releases/). - Access to a running OpenChoreo control plane (local
k3dor a deployed cluster). - OAuth client credentials for the OpenChoreo Identity Provider (used by the catalog sync and user sign-in). On
k3dthe helm chart pre-seeds these; for a deployed cluster see Identity configuration.
Sections 4β7 rely on Backstage's New Frontend System (@backstage/frontend-plugin-api / @backstage/frontend-defaults), which is available from Backstage 1.35 onward and stable from 1.40+. Adopters on Backstage < 1.40 should skip to Section 9 β Legacy frontend system (fallback), which manually mounts every OC tab into EntityPage.tsx. There's no separate doc for legacy β Section 9 covers the full recipe.
Adopters on 1.40 β€ Backstage < 1.51 may work but are untested. The pinned resolutions in Section 2 explicitly target the 1.51.0 line; older lines will need different pins. If you're on 1.51+, you may need to versions:bump --release 1.51.0 down to align; the same command works going up when we release against a newer line.
create-app@latest will likely give you Backstage 1.52.x or newer at the time you read this. After scaffolding, run yarn backstage-cli versions:bump --release 1.51.0 (covered in Section 2) to align with the tested combination.
2. Pin Backstage versionsβ
Add the following resolutions to your workspace package.json. They lock every @backstage/* package to the version line that the OpenChoreo plugins are tested against.
{
"resolutions": {
"@types/react": "^18",
"@types/react-dom": "^18",
"@mui/material": "^5.18.0",
"@backstage/frontend-plugin-api": "0.17.0",
"@backstage/frontend-defaults": "0.5.2",
"@backstage/plugin-app-react": "0.2.3",
"@backstage/plugin-scaffolder": "1.37.0"
}
}
Why each pin exists:
@mui/material: ^5.18.0β without this,material-ui-popup-stateresolves a nested copy of MUI 5.13.x that lacks the/versionsubpath and Rspack fails to compile.@backstage/frontend-plugin-api: 0.17.0,frontend-defaults: 0.5.2,plugin-app-react: 0.2.3βversions:bump --release 1.51.0writes carets (^0.5.2etc.) that yarn happily floats to a later minor whose transitive deps wantfrontend-plugin-api ^0.18.0. Multiple copies offrontend-plugin-apiland innode_modulesand TypeScript surfacesTS2742: The inferred type of 'SidebarContent' cannot be namedβ¦. Exact pins force one copy.@backstage/plugin-scaffolder: 1.37.0β 1.38.x importsBreadcrumbEntryfromfrontend-plugin-api ^0.18, which our pin at 0.17 doesn't export. The Rspack dev server surfaces this asESModulesLinkingWarningand paints a red "Compiled with problems" overlay; app still runs but the overlay is scary. 1.37.0 predates that import.
For the full pinned set see the compatibility matrix.
If your existing app is at a different Backstage release line, run:
yarn backstage-cli versions:bump --release 1.51.0
yarn install
β¦to align before adding the OpenChoreo packages.
3. Pre-approve the @openchoreo scopeβ
@openchoreo/* packages come from the public npm registry, so no authentication is needed β nothing to configure for access.
There is one thing worth configuring. The current create-app scaffold's .yarnrc.yml enables a 3-day npm minimum-age gate, which blocks packages published within the last three days. Pre-approve the scope so a fresh OpenChoreo release installs immediately instead of failing for three days after it ships:
nodeLinker: node-modules
npmMinimalAgeGate: 3d
npmPreapprovedPackages:
- "@backstage/*"
- "@openchoreo/*"
yarnPath: .yarn/releases/yarn-4.13.0.cjs
Releases from 1.3.0 onward are published from CI with npm trusted publishing and carry a signed provenance attestation tying the tarball to the workflow run that built it. Check your whole dependency tree with:
npm audit signatures
Versions 1.2.x and earlier were migrated from GitHub Packages and have no attestation. Versions older than 1.1.0 were not migrated and remain available only from GitHub Packages, which is frozen and receives no new releases.
4. Core installβ
Required. Delivers OpenChoreo sign-in, catalog sync (Domain/System/Component entities), and the Cell, Deploy, and Definition tabs on every entity page.
After this section, a user signs in via the OpenChoreo IDP, lands in the catalog, and clicks into an entity to see live Cell/Deploy/Definition data. Sections 5β7 build on this foundation; do them in any order.
The Deploy and Cell tabs read from OpenChoreo APIs that require a user IDP token (not the Backstage identity token). That token only exists after the user signs in via the OpenChoreo IDP. The catalog provider uses client-credentials and doesn't need user sign-in, but the tabs on those entities do. So sign-in lives in Core, not in an opt-in section.
If your OpenChoreo cluster runs with features.auth.enabled: false (no IDP), the same wiring still works β DynamicSignInPage (below) falls through to guest sign-in. See the cluster-mirroring warning in section 4.4.
4.1 Install packagesβ
In your app workspace:
yarn workspace app add \
@openchoreo/backstage-design-system@^1.2.0 \
@openchoreo/backstage-plugin-common@^1.2.0 \
@openchoreo/backstage-plugin-react@^1.2.0 \
@openchoreo/backstage-plugin@^1.2.0 \
@material-ui/lab@4.0.0-alpha.61
In your backend workspace:
yarn workspace backend add \
@openchoreo/openchoreo-client-node@^1.2.0 \
@openchoreo/openchoreo-auth@^1.2.0 \
@openchoreo/backstage-plugin-common@^1.2.0 \
@openchoreo/backstage-plugin-catalog-backend-module@^1.2.0 \
@openchoreo/backstage-plugin-permission-backend-module-openchoreo-policy@^1.2.0 \
@openchoreo/backstage-plugin-scaffolder-backend-module@^1.2.0 \
@openchoreo/backstage-plugin-backend@^1.2.0 \
@openchoreo/backstage-plugin-auth-backend-module-openchoreo-auth@^1.2.0
@openchoreo/* to the same minorThe packages above are linked together by the OpenChoreo release process β they always release with matching versions. Pinning them all to the same ^x.y.0 caret keeps your install on one consistent release line. If you want to track the cutting edge, swap ^1.2.0 for the next dist-tag: yarn workspace app add @openchoreo/backstage-plugin@next (etc.). Prereleases are published under next; stable releases are under latest.
4.2 Wire the backendβ
The NFS scaffold's packages/backend/src/index.ts already wires scaffolder, search, techdocs, kubernetes, notifications, signals, and mcp-actions for you. Add the OpenChoreo modules on top β don't replace the file.
Edit packages/backend/src/index.ts:
import { createBackend } from "@backstage/backend-defaults";
import { rootHttpRouterServiceFactory } from "@backstage/backend-defaults/rootHttpRouter";
import {
immediateCatalogServiceFactory,
annotationStoreFactory,
} from "@openchoreo/backstage-plugin-catalog-backend-module";
import { createIdpTokenHeaderMiddleware } from "@openchoreo/openchoreo-auth";
import { OpenChoreoAuthModule } from "@openchoreo/backstage-plugin-auth-backend-module-openchoreo-auth";
const backend = createBackend();
// OpenChoreo service factories.
// AnnotationStore is shared between the catalog module and openchoreo-backend.
// ImmediateCatalogService lets scaffolder actions register entities synchronously.
backend.add(immediateCatalogServiceFactory);
backend.add(annotationStoreFactory);
// IDP token middleware: reads x-openchoreo-token from frontend requests and
// stashes it in AsyncLocalStorage. The permission policy and tab backends
// pull from that context to authorize the signed-in user against OpenChoreo.
// MUST be added before applyDefaults() so the context exists when downstream
// handlers run.
backend.add(
rootHttpRouterServiceFactory({
configure: ({ app, applyDefaults, middleware }) => {
app.use(middleware.helmet());
app.use(middleware.cors());
app.use(middleware.compression());
app.use(createIdpTokenHeaderMiddleware());
applyDefaults();
},
}),
);
// ... keep all the existing scaffold backend.add(...) lines ...
// Sign-in: register OpenChoreo as an OIDC provider.
backend.add(OpenChoreoAuthModule);
// Replace the scaffold's `@backstage/plugin-permission-backend-module-allow-all-policy`
// with the OpenChoreo permission policy. The OpenChoreo policy delegates
// `openchoreo.*` permissions to the OpenChoreo authorization service and
// falls back to ALLOW for everything else, so it is composable with
// other host-app policies.
backend.add(
import("@openchoreo/backstage-plugin-permission-backend-module-openchoreo-policy"),
);
// IMPORTANT: register catalog-backend-module BEFORE openchoreo-backend.
// openchoreo-backend depends on the AnnotationStore initialized by the catalog module.
backend.add(import("@openchoreo/backstage-plugin-catalog-backend-module"));
backend.add(import("@openchoreo/backstage-plugin-backend"));
backend.add(import("@openchoreo/backstage-plugin-scaffolder-backend-module"));
backend.start();
If your existing index.ts registers @backstage/plugin-permission-backend-module-allow-all-policy, remove that line β only one permission policy can be active at a time.
4.3 Wire the frontendβ
Two sub-steps, both in packages/app/src/. Compared to the legacy install, you do not edit EntityPage.tsx β every Core OpenChoreo tab (Cell, Deploy, Definition) and overview card is contributed automatically by the plugin's /alpha export.
4.3.1 Add the OpenChoreo features to createAppβ
A fresh NFS scaffold's packages/app/src/App.tsx is roughly:
import { createApp } from "@backstage/frontend-defaults";
import catalogPlugin from "@backstage/plugin-catalog/alpha";
import { navModule } from "./modules/nav";
export default createApp({
features: [catalogPlugin, navModule],
});
Add the OpenChoreo features:
import { createApp } from "@backstage/frontend-defaults";
import catalogPlugin from "@backstage/plugin-catalog/alpha";
import openchoreoPluginAlpha, {
openChoreoAppModule,
openChoreoEntityGroupsModule,
} from "@openchoreo/backstage-plugin/alpha";
import { navModule } from "./modules/nav";
import { customAppModule } from "./customAppModule";
export default createApp({
features: [
catalogPlugin,
openchoreoPluginAlpha,
openChoreoAppModule,
openChoreoEntityGroupsModule,
navModule,
customAppModule, // see 4.3.2 below
],
});
What each one contributes:
openchoreoPluginAlphaβ every OC entity tab (Cell, Deploy, Definition, Build, Logs, Metrics, β¦), the OC overview cards (Project Contents, Deployment Pipeline, Deployments widget, β¦), theopenchoreo:inject-user-tokenscaffolder form decorator, and the OC scaffolder field extensions (used by CTD-generated templates).openChoreoAppModuleβ required. Wraps the app root inOpenChoreoQueryProvider(shared TanStack Query client used by every OC surface) and overridesfetchApiRef+permissionApiRefso every request carries the signed-in user's IDP token asx-openchoreo-token. Without it, entity pages crash on first tab render (No QueryClient set), and every OC-API call 401s.openChoreoEntityGroupsModuleβ recommended. Registers the canonical OC tab order (Definition β Build β Deploy β Cell Diagram β Diagram β Logs β β¦) viapage:catalog/entity'sgroupDefinitions. Without it, vanilla NFS collapses tabs that share agroup:value (Build + Deploy) into a Deployment βΎ dropdown. Non-OC entity pages are unaffected β the module retains all six upstream default group names in their vanilla relative order.
4.3.2 Author the customAppModuleβ
Only the SignInPage stays consumer-side; the auth API ref, fetch override, permission override, and query provider all ship inside openChoreoAppModule (added to features: in 4.3.1) so the file is small.
Create packages/app/src/customAppModule.tsx:
import {
configApiRef,
createFrontendModule,
useApi,
} from "@backstage/frontend-plugin-api";
import { SignInPageBlueprint } from "@backstage/plugin-app-react";
import { SignInPage } from "@backstage/core-components";
import { openChoreoAuthApiRef } from "@openchoreo/backstage-plugin";
// The base plugin (via `openChoreoAppModule` in App.tsx) ships the auth /
// fetch / permission API blueprints out of the box. All that is left for
// the host app is the SignInPage β it stays here because the host picks
// between OpenChoreo sign-in and guest sign-in.
function DynamicSignInPage(props: any) {
const configApi = useApi(configApiRef);
const authEnabled =
configApi.getOptionalBoolean("openchoreo.features.auth.enabled") ?? true;
if (!authEnabled) {
return <SignInPage {...props} auto providers={["guest"]} />;
}
return (
<SignInPage
{...props}
provider={{
id: "openchoreo-auth",
title: "OpenChoreo",
message: "Sign in using OpenChoreo",
apiRef: openChoreoAuthApiRef,
}}
/>
);
}
export const customAppModule = createFrontendModule({
pluginId: "app",
extensions: [
SignInPageBlueprint.make({
params: {
loader: async () => DynamicSignInPage,
},
}),
],
});
openchoreo.features.auth.enabled is read in both places (4.3.2 here and 4.2 on the backend). If the backend module sees it as true but DynamicSignInPage falls through to guest, the OpenChoreo authz API will reject the guest token. Set it once in app-config and let both sides read it.
4.3.3 Entity-page tabs & scaffolder fields β already doneβ
Under NFS, every Core OpenChoreo tab is contributed by openchoreoPluginAlpha as an EntityContentBlueprint extension, and every OC scaffolder form field (used by CTD-generated templates) as a FormFieldBlueprint extension. Same for the openchoreo:inject-user-token form decorator β CTD-generated templates that opt into it via EXPERIMENTAL_formDecorators: [{ id: openchoreo:inject-user-token }] in their spec get the signed-in user's token injected as the OPENCHOREO_USER_TOKEN template secret at submit time.
You don't need to edit EntityPage.tsx (the NFS scaffold does not even create one) or register any of these by hand.
After Section 4.5 β Verify you should see (with openChoreoEntityGroupsModule installed β canonical order, flat tabs):
- Domain pages:
OVERVIEW,TECHDOCS,DEFINITION,CELL DIAGRAM. - System pages:
OVERVIEW,TECHDOCS,DEFINITION,DEPLOY,CELL DIAGRAM,DIAGRAMβ plus auto-mounted overview cards (Project Contents, Deployment Pipeline, etc.). - Component pages:
OVERVIEW,TECHDOCS,APIS,DEFINITION,DEPLOYβ plus the Deployments widget on Overview. AddopenchoreoCiPluginAlpha(Section 6) to get aBUILDtab in the same row.
If you skipped openChoreoEntityGroupsModule, tabs sharing a group: value collapse into a Deployment βΎ dropdown per vanilla NFS behavior β functionality is unchanged, but you lose the flat top-level layout.
If you need to override where a tab appears or hide one for a specific kind, see Entity views β customizing under NFS.
4.4 Configure app-configβ
Add an openchoreo block to your app-config.yaml (or app-config.local.yaml for development).
openchoreo.features.auth.enabled and openchoreo.features.authz.enabled are not independent installer defaults. They must match how the OpenChoreo cluster your Backstage instance is talking to was deployed:
| Cluster runs with⦠| Set in Backstage |
|---|---|
| auth + authz both off (local dev mode) | auth.enabled: false, authz.enabled: false |
| auth + authz both on (production mode) | auth.enabled: true, authz.enabled: true |
The in-tree OpenChoreo portal hides this knob because the Helm chart configures both halves in lockstep. External Backstage hosts installing the published @openchoreo/* plugins bypass the chart and must align by hand. Mismatch produces failure modes covered in Troubleshooting: the catalog provider 401s if auth disagrees, and tabs render "No permissions" if authz disagrees.
Check your cluster: look at the features block in the OpenChoreo helm values, or ask whoever deployed it.
auth:
environment: development
providers:
openchoreo-auth:
development:
clientId: ${OPENCHOREO_AUTH_CLIENT_ID}
clientSecret: ${OPENCHOREO_AUTH_CLIENT_SECRET}
# Set the OAuth2 endpoints explicitly. `metadataUrl` (OIDC discovery)
# is *not* used because the default OpenChoreo IDP (ThunderID) does not
# expose a reliable /.well-known/openid-configuration endpoint on k3d
# β uncomment it only if your IDP advertises one.
# metadataUrl: ${OPENCHOREO_AUTH_METADATA_URL}
authorizationUrl: ${OPENCHOREO_AUTH_AUTHORIZATION_URL}
tokenUrl: ${OPENCHOREO_AUTH_TOKEN_URL}
scope: "openid profile email"
guest:
dangerouslyAllowOutsideDevelopment: true
openchoreo:
baseUrl: ${OPENCHOREO_API_URL} # e.g. http://api.openchoreo.localhost:8080/api/v1
# OAuth2 client credentials used by the catalog provider for
# service-to-service auth (client_credentials flow). Required even when
# features.auth.enabled is false, because the catalog provider still needs
# to fetch namespaces/projects/components from the OpenChoreo API.
auth:
clientId: ${OPENCHOREO_CLIENT_ID}
clientSecret: ${OPENCHOREO_CLIENT_SECRET}
tokenUrl: ${OPENCHOREO_TOKEN_URL}
features:
# MIRROR THE CLUSTER (see warning above). Backend and frontend both read
# these β they must agree across the whole stack.
auth:
enabled: true
authz:
enabled: true
defaultOwner: openchoreo-users
schedule:
frequency: 30 # seconds between catalog provider runs
timeout: 120 # seconds for catalog provider timeout
permission:
enabled: true # required for the OpenChoreo permission policy to run
# The catalog must accept every kind the OpenChoreo provider emits. Missing
# `Template` here silently drops the CTD-generated create-component templates
# β scaffolder submissions then 404 with "Template β¦ not found". Missing
# `Environment` drops the Environment entities the provider emits for the
# Deploy tab and Deployment Pipeline card.
catalog:
rules:
- allow:
[
Component,
System,
Domain,
API,
Resource,
Location,
Group,
User,
Template,
Environment,
]
Local k3d quickstartβ
OpenChoreo's k3d helm install seeds known credentials. Skip the ${VAR} placeholders and hardcode these values to get sign-in working without touching env vars:
auth:
environment: development
providers:
openchoreo-auth:
development:
clientId: openchoreo-backstage-client
clientSecret: backstage-portal-secret
authorizationUrl: http://thunder.openchoreo.localhost:8080/oauth2/authorize
tokenUrl: http://thunder.openchoreo.localhost:8080/oauth2/token
scope: "openid profile email"
openchoreo:
baseUrl: http://api.openchoreo.localhost:8080/api/v1
auth:
clientId: openchoreo-backstage-client
clientSecret: backstage-portal-secret
tokenUrl: http://thunder.openchoreo.localhost:8080/oauth2/token
These are k3d dev cluster seed values, not real secrets β never use them against a production OpenChoreo install. For deployed clusters see Identity configuration.
4.5 Verifyβ
yarn start
Expected:
- Backend logs show
Plugin initialization completeforopenchoreo,catalog,permission,auth. - After ~30s:
Successfully processed N entities (X domains, Y systems, Z components, ...). - Browser β
http://localhost:3000β sign-in page shows "Sign in using OpenChoreo." Click through and land in the catalog. http://localhost:3000/catalog?filters[kind]=domainβ see your OpenChoreo namespaces.- Click into any project (
kind=system) β CELL DIAGRAM and DEFINITION tabs are present and render real data. - Click into any component β DEPLOY and DEFINITION tabs are present and render real data.
If you get a 401 on tab data fetches, openChoreoAppModule isn't in your features: β every OC-API request needs the fetch-override it ships. If entity pages crash with No QueryClient set, use QueryClientProvider to set one, same fix (that module also ships the shared OpenChoreoQueryProvider as an app-root wrapper). If you get "No permissions" on Deploy tabs, again check that openChoreoAppModule is registered.
See Troubleshooting for the full failure-mode index.
5. Add Observability tabs (optional)β
Adds Logs, Events, Metrics, Alerts, Wirelogs to Component pages and Logs, Traces, Incidents, RCA Reports, Cost Analysis to System pages. Built on top of Core.
Prerequisites: Section 4 (Core) complete and verified.
5.1 Install packagesβ
yarn workspace app add @openchoreo/backstage-plugin-openchoreo-observability@^1.2.0
yarn workspace backend add @openchoreo/backstage-plugin-openchoreo-observability-backend@^1.2.0
5.2 Wire the backendβ
Append to packages/backend/src/index.ts:
backend.add(
import("@openchoreo/backstage-plugin-openchoreo-observability-backend"),
);
5.3 Wire the frontendβ
Append the observability Alpha feature to createApp({ features: [...] }):
import openchoreoObservabilityPluginAlpha from "@openchoreo/backstage-plugin-openchoreo-observability/alpha";
export default createApp({
features: [
catalogPlugin,
openchoreoPluginAlpha,
openChoreoAppModule,
openChoreoEntityGroupsModule,
openchoreoObservabilityPluginAlpha, // <-- add
navModule,
customAppModule,
],
});
No EntityPage.tsx edits. Every observability tab auto-mounts on the correct entity kind.
5.4 Configureβ
Append to the openchoreo.features block in app-config.local.yaml:
openchoreo:
features:
observability:
enabled: true
5.5 Verifyβ
Restart yarn start. Open any Component β the LOGS, EVENTS, METRICS, ALERTS, WIRELOGS tabs appear. Open any System β LOGS, TRACES, INCIDENTS, RCA REPORTS, COST ANALYSIS tabs appear. If a tab renders an empty state, the backend reached OpenChoreo but no data is available yet for that scope. If a tab errors, see Troubleshooting.
6. Add CI/Build tab (optional)β
Adds a Build tab to Component pages, showing workflow runs and a parameter-form trigger. Built on top of Core.
Prerequisites: Section 4 (Core) complete and verified.
6.1 Install packagesβ
yarn workspace app add @openchoreo/backstage-plugin-openchoreo-ci@^1.2.0
yarn workspace backend add @openchoreo/backstage-plugin-openchoreo-ci-backend@^1.2.0
6.2 Wire the backendβ
Append to packages/backend/src/index.ts:
backend.add(import("@openchoreo/backstage-plugin-openchoreo-ci-backend"));
6.3 Wire the frontendβ
Append the CI Alpha feature to createApp({ features: [...] }):
import openchoreoCiPluginAlpha from "@openchoreo/backstage-plugin-openchoreo-ci/alpha";
export default createApp({
features: [
catalogPlugin,
// Register CI *before* the base plugin so the Build tab sorts to the
// left of Deploy under `openChoreoEntityGroupsModule`'s ordering.
openchoreoCiPluginAlpha, // <-- add
openchoreoPluginAlpha,
openChoreoAppModule,
openChoreoEntityGroupsModule,
navModule,
customAppModule,
],
});
6.4 Configureβ
Append to the openchoreo.features block:
openchoreo:
features:
workflows:
enabled: true
6.5 Verifyβ
Restart yarn start. Open any Component β the BUILD tab appears. It lists past workflow runs and (depending on permissions) offers a "Trigger build" form.
7. Add standalone Workflows page (optional)β
Adds an org-level /workflows page for generic (non-component-scoped) workflow templates and runs, plus a Runs tab on Workflow and ClusterWorkflow entities. Built on top of Core.
Prerequisites: Section 4 (Core) complete and verified.
7.1 Install packagesβ
yarn workspace app add @openchoreo/backstage-plugin-openchoreo-workflows@^1.2.0
yarn workspace backend add @openchoreo/backstage-plugin-openchoreo-workflows-backend@^1.2.0
7.2 Wire the backendβ
Append to packages/backend/src/index.ts:
backend.add(
import("@openchoreo/backstage-plugin-openchoreo-workflows-backend"),
);
7.3 Wire the frontendβ
Append the workflows Alpha feature:
import openchoreoWorkflowsPluginAlpha from "@openchoreo/backstage-plugin-openchoreo-workflows/alpha";
export default createApp({
features: [
catalogPlugin,
openchoreoPluginAlpha,
openChoreoAppModule,
openChoreoEntityGroupsModule,
openchoreoWorkflowsPluginAlpha, // <-- add
navModule,
customAppModule,
],
});
The plugin contributes the standalone /workflows page as a PageBlueprint β no <Route> mount needed.
Optionally add a sidebar entry in packages/app/src/modules/nav/Sidebar.tsx so users can navigate to it.
7.4 Verifyβ
Restart yarn start. Navigate to http://localhost:3000/workflows β see the org-level workflow list.
8. Troubleshootingβ
If anything misbehaves at any step, see Troubleshooting for the full failure-mode index, indexed by exact error message.
Common issues we've seen during external-app testing:
TS2742: The inferred type of 'SidebarContent' cannot be namedβ¦β resolution drift afterversions:bump --release 1.51.0. Section 2's resolutions include the threefrontend-plugin-api-related pins that fix this.ESModulesLinkingWarning: export 'BreadcrumbEntry' β¦ was not found+ red "Compiled with problems" overlay β the@backstage/plugin-scaffolder: 1.37.0pin in Section 2's resolutions fixes this. Under the covers,1.38.ximportsBreadcrumbEntryfromfrontend-plugin-api ^0.18, which we're pinned to 0.17 for the reason above.No QueryClient set, use QueryClientProvider to set onewhen clicking into an entity βopenChoreoAppModuleisn't infeatures:. The app-root query provider ships from there.API_FACTORY_CONFLICT: API 'core.fetch' is already provided by plugin 'app'at app startup β the fetch/permission overrides were previously copy-pasted intocustomAppModule. Delete thoseApiBlueprintcalls; they now live inopenChoreoAppModule.Backend request failed, 404 Not Found β¦ Template template:openchoreo-cluster/template-* not foundat scaffolder submit βTemplateis missing fromcatalog.rulesinapp-config.yaml. See Section 4.4's allow-list.Failed to get authentication token. Ensure you are logged in.at scaffolder submit βopenchoreoPluginAlphaisn't installed (it ships theopenchoreo:inject-user-tokenform decorator), or the user isn't signed in via OpenChoreo auth.
9. Legacy frontend system (fallback)β
If your Backstage host is scaffolded with --legacy (or you have heavy EntityPage.tsx customization you want to keep), follow the legacy wiring below instead of Sections 4.3 through 7. Sections 1β3 and 4.1β4.2 (prereqs, resolutions, scope pre-approval, package install, backend wiring) are identical.
The legacy install registers each plugin's default export via createApp({ apis, plugins, bindRoutes }) and manually mounts every entity tab into components/catalog/EntityPage.tsx.
Do NOT mix the two. If you import openchoreoPluginAlpha from /alpha and also mount <Environments /> in EntityPage.tsx, you will see duplicate tabs (or the wrong tab will win depending on extension load order).
9.1 Wire the frontend (legacy)β
In packages/app/src/App.tsx:
import { choreoPlugin } from '@openchoreo/backstage-plugin';
const app = createApp({
apis,
plugins: [choreoPlugin], // <-- add; opt-in sections will append more
bindRoutes({ bind }) { ... },
});
Wire OpenChoreo sign-in by creating packages/app/src/apis/authRefs.ts:
import {
ApiRef,
BackstageIdentityApi,
createApiRef,
OAuthApi,
ProfileInfoApi,
SessionApi,
} from "@backstage/core-plugin-api";
export const openChoreoAuthApiRef: ApiRef<
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
> = createApiRef({
id: "auth.openchoreo-auth",
});
Register the OAuth2 factory in packages/app/src/apis.ts:
import { OAuth2 } from "@backstage/core-app-api";
import { openChoreoAuthApiRef } from "./apis/authRefs";
export const apis: AnyApiFactory[] = [
// ... your existing factories ...
createApiFactory({
api: openChoreoAuthApiRef,
deps: {
discoveryApi: discoveryApiRef,
oauthRequestApi: oauthRequestApiRef,
configApi: configApiRef,
},
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
OAuth2.create({
discoveryApi,
oauthRequestApi,
configApi,
provider: {
id: "openchoreo-auth",
title: "OpenChoreo",
icon: () => null,
},
environment: configApi.getOptionalString("auth.environment"),
defaultScopes: ["openid", "profile", "email"],
}),
}),
];
The OpenChoreoFetchApi and OpenChoreoPermissionApi classes from Section 4.3.2 above are registered as legacy createApiFactory entries in apis.ts (same shape as upstream apiFactories). Pass components: { SignInPage: DynamicSignInPage } to createApp for the sign-in slot.
9.2 Mount entity tabsβ
Add the Core tab components to packages/app/src/components/catalog/EntityPage.tsx:
import {
Environments,
CellDiagram,
ResourceDefinitionTab,
} from '@openchoreo/backstage-plugin';
// componentEntityPage / defaultEntityPage:
<EntityLayout.Route path="/definition" title="Definition">
<ResourceDefinitionTab />
</EntityLayout.Route>
<EntityLayout.Route path="/environments" title="Deploy">
<Environments />
</EntityLayout.Route>
// systemPage:
<EntityLayout.Route path="/definition" title="Definition">
<ResourceDefinitionTab />
</EntityLayout.Route>
<EntityLayout.Route path="/cell" title="Cell">
<CellDiagram />
</EntityLayout.Route>
// domainPage:
<EntityLayout.Route path="/definition" title="Definition">
<ResourceDefinitionTab />
</EntityLayout.Route>
For the legacy opt-in tab packs (Observability / CI / Workflows), see Entity views β Legacy wiring example. The wiring is the same as the 1.1.x install guide β plugins: [..., openchoreoObservabilityPlugin] plus the per-tab <EntityLayout.Route> blocks.