Skip to main content
Version: v1.3.0-rc.1 (pre-release)

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 to createApp({ features: [...] }), and every OpenChoreo entity tab and overview card auto-mounts. No EntityPage.tsx edits. 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 heavy EntityPage.tsx customization 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.

info

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.

Tracking the upcoming 1.2.0 release

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-app scaffold ships Yarn 4.13.0 in .yarn/releases/).
  • Access to a running OpenChoreo control plane (local k3d or a deployed cluster).
  • OAuth client credentials for the OpenChoreo Identity Provider (used by the catalog sync and user sign-in). On k3d the helm chart pre-seeds these; for a deployed cluster see Identity configuration.
Backstage version β€” auto-mounting requires NFS

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.

Backstage version

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.

package.json
{
"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-state resolves a nested copy of MUI 5.13.x that lacks the /version subpath 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.0 writes carets (^0.5.2 etc.) that yarn happily floats to a later minor whose transitive deps want frontend-plugin-api ^0.18.0. Multiple copies of frontend-plugin-api land in node_modules and TypeScript surfaces TS2742: The inferred type of 'SidebarContent' cannot be named…. Exact pins force one copy.
  • @backstage/plugin-scaffolder: 1.37.0 β€” 1.38.x imports BreadcrumbEntry from frontend-plugin-api ^0.18, which our pin at 0.17 doesn't export. The Rspack dev server surfaces this as ESModulesLinkingWarning and 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:

.yarnrc.yml
nodeLinker: node-modules
npmMinimalAgeGate: 3d
npmPreapprovedPackages:
- "@backstage/*"
- "@openchoreo/*"

yarnPath: .yarn/releases/yarn-4.13.0.cjs
Verifying what you installed

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.

Why sign-in is part of Core

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
Pinning all @openchoreo/* to the same minor

The 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:

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:

packages/app/src/App.tsx
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:

packages/app/src/App.tsx
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, …), the openchoreo:inject-user-token scaffolder form decorator, and the OC scaffolder field extensions (used by CTD-generated templates).
  • openChoreoAppModule β€” required. Wraps the app root in OpenChoreoQueryProvider (shared TanStack Query client used by every OC surface) and overrides fetchApiRef + permissionApiRef so every request carries the signed-in user's IDP token as x-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 β†’ …) via page:catalog/entity's groupDefinitions. Without it, vanilla NFS collapses tabs that share a group: 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:

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,
},
}),
],
});
Backend and frontend auth flags must agree

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. Add openchoreoCiPluginAlpha (Section 6) to get a BUILD tab 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).

Feature flags must mirror the OpenChoreo cluster

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.

app-config.local.yaml
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:

app-config.local.yaml (k3d-only)
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:

  1. Backend logs show Plugin initialization complete for openchoreo, catalog, permission, auth.
  2. After ~30s: Successfully processed N entities (X domains, Y systems, Z components, ...).
  3. Browser β†’ http://localhost:3000 β†’ sign-in page shows "Sign in using OpenChoreo." Click through and land in the catalog.
  4. http://localhost:3000/catalog?filters[kind]=domain β†’ see your OpenChoreo namespaces.
  5. Click into any project (kind=system) β†’ CELL DIAGRAM and DEFINITION tabs are present and render real data.
  6. 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: [...] }):

packages/app/src/App.tsx
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: [...] }):

packages/app/src/App.tsx
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:

packages/app/src/App.tsx
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 after versions:bump --release 1.51.0. Section 2's resolutions include the three frontend-plugin-api-related pins that fix this.
  • ESModulesLinkingWarning: export 'BreadcrumbEntry' … was not found + red "Compiled with problems" overlay β€” the @backstage/plugin-scaffolder: 1.37.0 pin in Section 2's resolutions fixes this. Under the covers, 1.38.x imports BreadcrumbEntry from frontend-plugin-api ^0.18, which we're pinned to 0.17 for the reason above.
  • No QueryClient set, use QueryClientProvider to set one when clicking into an entity β€” openChoreoAppModule isn't in features:. 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 into customAppModule. Delete those ApiBlueprint calls; they now live in openChoreoAppModule.
  • Backend request failed, 404 Not Found … Template template:openchoreo-cluster/template-* not found at scaffolder submit β€” Template is missing from catalog.rules in app-config.yaml. See Section 4.4's allow-list.
  • Failed to get authentication token. Ensure you are logged in. at scaffolder submit β€” openchoreoPluginAlpha isn't installed (it ships the openchoreo:inject-user-token form 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.

Choose one path

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:

packages/app/src/App.tsx (legacy host)
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:

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:

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:

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.