Skip to main content

Configure Backstage

The Portal Controller writes entities to a repository. Backstage has to be told to read them, and needs the SKE plugins installed so that submitting a generated Template does something.

This page covers the Backstage side. Configure the portal itself first, see Backstage.

Prerequisites

  • Backstage app running v1.40 or above: The SKE plugins are tested up to v1.50.4. If you do not have a Backstage app, refer to the Backstage official documentation for instructions on how to create a Backstage app.

  • Node.js 22+: Starting from Backstage v1.50, Node.js 22 or higher is required due to native module dependencies. Ensure your build environment and Docker images use Node 22+.

  • NODE_OPTIONS environment variable: Set the following when starting your Backstage app:

    export NODE_OPTIONS=--no-node-snapshot

Accessing the private npm registry

The SKE plugins are distributed through a private npm registry. Please follow the steps below to enable access to it.

To access the private npm registry on your local machine, you will need npm config in a local ~/.npmrc file in your home directory. Create this file if it doesn't already exist, and add the following content:

# replace <YOUR_TOKEN_HERE> with the provided Syntasso registry token
@syntasso:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken="<YOUR_TOKEN_HERE>"
always-auth=true
info

For Yarn 3+, use the following command to set a local .yarnrc.yml file:

yarn config set npmScopes --json '{"syntasso": {"npmAlwaysAuth": true, "npmAuthToken": "<YOUR TOKEN HERE>", "npmRegistryServer": "https://npm.pkg.github.com"}}'

Install the plugins

Two packages, one for each half of Backstage:

yarn add @syntasso/plugin-ske-backend --cwd packages/backend
yarn add @syntasso/plugin-ske-frontend @backstage/plugin-permission-react --cwd packages/app
Peer dependencies (v0.21.1 and later)

From @syntasso/plugin-ske-frontend v0.21.1, the packages the frontend plugin shares with the host app (@backstage/plugin-catalog, @backstage/plugin-catalog-graph, @backstage/plugin-catalog-react, @backstage/plugin-scaffolder, @backstage/plugin-scaffolder-react and @backstage/plugin-permission-react) are declared as peer dependencies, so your app must provide them. Most already ship with a standard Backstage app; the one that usually does not is @backstage/plugin-permission-react, which is why it is added explicitly above.

Yarn does not fail the install over a missing peer — it prints a warning (YN0002: … doesn't provide @backstage/plugin-permission-react) and yarn install still completes, so it is easy to miss until the app fails to build against the absent package.

important

@syntasso/plugin-ske-backend must be at least v0.21.0, and @syntasso/plugin-ske-frontend at least v0.18.0. Earlier backend versions do not provide the action that pull-request mode uses to record a request, so a submission opens the pull request and then fails.

Spotify Portal for Backstage

If you are using Spotify Portal for Backstage, publish the plugins to your cloud instance before completing the install — see the Spotify Portal installation guide.

Backend

Add the plugin in packages/backend/src/index.ts:

// SKE plugin
backend.add(import('@syntasso/plugin-ske-backend'));

That single line registers the scaffolder actions the generated Templates use, the routes the frontend calls, and the catalogue provider that surfaces pending requests.

The plugin creates one database table and runs its migrations at startup. If you run more than one Backstage replica, use PostgreSQL rather than SQLite.

Frontend

If your Backstage app uses the new frontend system (default since Backstage v1.49.0), the plugin is automatically discovered — no changes to EntityPage.tsx are needed. This is also the case if you are using Spotify Portal for Backstage.

The plugin registers the following extensions via its alpha entry point:

  • Promise Overview — displays the Overview tab for Promise entities
  • Resource Overview — displays the Overview tab for Resource entities
  • Resource Manage — displays the Manage tab for Resource entities

To verify the plugin is loaded, check your Backstage app configuration. The plugin should appear under @syntasso/plugin-ske-frontend/alpha in the resolved plugin list.

In packages/app/src/components/catalog/EntityPage.tsx, import the entity pages:

import {
KratixPromiseEntityPage,
KratixResourceEntityPage,
isKratixType,
} from '@syntasso/plugin-ske-frontend';

Then add two cases to the existing componentPage switch, before the default case:

<EntitySwitch.Case if={isKratixType('promise')}>
<KratixPromiseEntityPage>{entityWarningContent}</KratixPromiseEntityPage>
</EntitySwitch.Case>

<EntitySwitch.Case if={isKratixType('resource')}>
<KratixResourceEntityPage>
{entityWarningContent}
</KratixResourceEntityPage>
</EntitySwitch.Case>

The matcher selects Promise and Resource Components using either the kratix.io/type annotation or spec.type. For example, a Resource can use kratix.io/type: resource or spec.type: kratix-resource.

warning

Add kratix.io/type: promise to every Promise Component. Although the route matcher recognises spec.type: kratix-promise, the page also needs the annotation when it loads Promise data. Without it, the Promise Overview does not load. It shows a missing-annotation message for Resource fields instead of the Promise details.

This gives Promise entities an overview page, and Resource entities an overview page plus a Manage tab for updating and deleting the resource. For the full list of entity pages, components, annotations, and behaviours, see the SKE Frontend Plugin reference.

Configuring the backend plugin

The plugin can read from and write to your Kratix platform in three ways: directly via the Kubernetes API, indirectly via a Git repository (GitOps mode), or a combination of the two that reads via the Kubernetes API and writes via a Git repository (Hybrid mode). Follow the decision guidance below to decide on the approach most fitting for your platform.

ske.mode decides how an approved request reaches the platform:

ModeReads fromWrites via
kubernetes (default)Kubernetes APIKubernetes API, as the signed-in user
gitopsGitGit
hybridKubernetes APIGit

This is backend-wide and independent of a portal's delivery mode. The same generated Template works under all three.

If you use pull-request delivery mode, you can tune how often Backstage re-checks which requests are still awaiting approval:

ske:
pendingRequests:
refreshSeconds: 60 # default

Configure Backend Access

Choose the mode that best fits your environment and operational requirements:

Kubernetes APIGitOpsHybrid
Real-time status updates
Full resource management (kubectl-compatible)
Requires direct network access to the platform cluster
Routes all changes through a Git repository

Choose Kubernetes API mode if Backstage can reach the platform cluster over your network. This provides the most complete feature set, including real-time status updates and full resource management.

Choose GitOps mode if Backstage cannot access the platform cluster directly, or if your organisation requires all platform changes to flow through a Git repository.

Choose Hybrid mode if Backstage can reach the platform cluster over your network and your organisation requires all platform changes to flow through a Git repository. Reads use the Kubernetes API (real-time status and visibility of resources created outside Backstage), while create, update, and delete operations are written to Git.

The plugin uses the Backstage Kubernetes integration to talk to the platform cluster. Once you configure it, you should have a kubernetes section in your app-config.yaml file similar to the following:

kubernetes:
serviceLocatorMethod:
type: multiTenant
clusterLocatorMethods:
- type: config
clusters:
- url: <my platform cluster url>
name: kratixPlatform
authProvider: serviceAccount
skipTLSVerify: true
serviceAccountToken: <service-account-token>
- url: <another kubernetes cluster url>
name: someOtherCluster
authProvider: serviceAccount
serviceAccountToken: <service-account-token>

Using OIDC Authentication

If your Backstage instance authenticates users via an OIDC provider, you can configure the backend plugin to forward the user's OIDC token to the platform cluster. This allows Kubernetes RBAC to enforce access control based on the logged-in Backstage user's identity, rather than using a shared service account.

To enable this, set authProvider: oidc and oidcTokenProvider: <your-provider-name> on the cluster in your kubernetes config:

kubernetes:
serviceLocatorMethod:
type: multiTenant
clusterLocatorMethods:
- type: config
clusters:
- url: <my platform cluster url>
name: kratixPlatform
authProvider: oidc
oidcTokenProvider: <your-provider-name> # e.g. keycloak, okta, auth0
skipTLSVerify: true

See the Configuring OIDC Authentication guide for full setup instructions.

You must now tell the backend plugin which of the configured clusters is the platform cluster. For that, add the following section to your app-config.yaml:

ske:
kubernetes:
# make sure this matches the name of the cluster you want to use
platformName: kratixPlatform

If no ske.kubernetes section is present, the backend plugin will assume Backstage itself is running on the Platform cluster and will load the cluster configuration from the local machine/pod.

Connect the Platform to the Git Repository

note

This step is required for GitOps and Hybrid modes, which write resource requests to a Git repository. It is not needed for Kubernetes API mode.

Your platform needs to watch the Git repository and reconcile any documents that Backstage pushes to it. The steps below cover two common GitOps tools — choose the one your platform uses.

Create a GitRepository source pointing at the same repository you configured in ske.scm:

apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: backstage-resources
namespace: flux-system
spec:
interval: 10s
url: https://github.com/my-org/my-repo # must match ske.scm.repoUrl
ref:
branch: main # must match ske.scm.branch

Then create a Kustomization to reconcile documents from that source:

apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: backstage-resources
namespace: flux-system
spec:
interval: 10s
sourceRef:
kind: GitRepository
name: backstage-resources
path: ./catalog # must match ske.scm.path
prune: true

Apply both resources to your platform cluster:

kubectl apply -f gitrepository.yaml -f kustomization.yaml
tip

For private repositories, you will need to create a Flux secret with your Git credentials and reference it in the GitRepository via spec.secretRef. See the Flux documentation for details.

Give Backstage access to GitHub

integrations:
github:
- host: github.com
token: ${GITHUB_TOKEN}

The credential needs:

  • Contents: read on the catalogue repository the Portal Controller writes to.
  • Pull requests: write on the repositories developers raise requests against, for pull-request mode.
  • Contents: write on the requests repository, if you use ske.mode: gitops or hybrid.
warning

Backstage never writes the catalogue repository, Kratix does, using the state store's own credential. But Backstage does read it. A token scoped only to the request repositories gives you working submissions and a silently empty catalogue.

Discover the generated entities

The Portal Controller writes Promise entities and Resource entities into two separate trees, so configure one catalogue provider for each:

catalog:
providers:
github:
platformPromiseCatalog:
organization: acme
catalogPath: /gitops/backstage/prod/dependencies/**/catalog-info.yaml
filters:
branch: main
repository: platform-catalog
schedule:
frequency: { minutes: 1 }
timeout: { minutes: 3 }
platformResourceCatalog:
organization: acme
catalogPath: /gitops/backstage/prod/resources/**/catalog-info.yaml
filters:
branch: main
repository: platform-catalog
schedule:
frequency: { minutes: 1 }
timeout: { minutes: 3 }

Fill the paths in from your portal's configuration:

PlaceholderComes from
gitops/backstage/prodThe state store's path, then the Destination's path
repositoryThe repository in the state store's url
branchThe state store's branch
organizationYour GitHub organisation

Two narrow providers rather than one glob over the whole prefix, because catalogPath takes a single glob and a wider one would ingest anything else that lands under it.

The default catalogue rules already permit Component and Template, so no catalog.rules change is needed.

Verify

Once Backstage has run a catalogue refresh you should see:

  • A Component for each Promise bound to the portal, with a matching scaffolder Template on the Create page.
  • A Component for each Resource request, with Manage available on it.

If entities never appear, check the catalogue repository actually contains catalog-info.yaml files under the two paths above, and that the GitHub credential can read that repository.