Scalr logo

Backstage Scalr Plugin

Created by Scalr

Scalr is a platform for Terraform and Open Tofu. It helps platform teams offer self service while keeping guardrails in place. You get a private module registry, policy checks, role based access, cost and run visibility, plus flexible workflows for both CLI users and pull request driven teams. That mix makes it easier to standardize how infrastructure gets built without slowing developers.

The Scalr plugin brings that workflow into Backstage. It lets you link Scalr environments and workspaces to Backstage entities so teams can see status where they already work. The plugin adds a modules page in the sidebar with sorting by namespaces. Teams can filter what appears by environment or by tag through simple annotations on their entities. Admins can allow users to trigger runs from Backstage when that makes sense for the org.

Common use cases are straightforward. Keep each service page tied to the exact workspaces that manage it. Put your module catalog in front of developers so they reuse what you trust. Review and approve changes with less context switching since run details are in the portal. If you are rolling out self service for Terraform or Open Tofu, this plugin gives you a clear path to meet developers where they are. Scalr announced the plugin in June 2025.

Installation Instructions

These instructions apply to self-hosted Backstage only.

Install the backend plugin

Copy
yarn workspace backend add @scalr-io/backstage-plugin-scalr-backend

New backend

Add the plugin module to your backend

Copy
// packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';

const backend = createBackend();

// other backend.add calls

backend.add(import('@scalr-io/backstage-plugin-scalr-backend'));

backend.start();

Old backend

Use a router if your app still uses the legacy backend system

Create a plugin router file

Copy
// packages/backend/src/plugins/scalr.ts
import { Router } from 'express';
// adjust this import path to your app setup
import { PluginEnvironment } from '../types';

// If the package exposes createRouter use it
// If your build fails with missing export then the plugin does not ship a legacy router
// In that case move to the new backend path shown above
import { createRouter } from '@scalr-io/backstage-plugin-scalr-backend';

export default async function createPlugin(
  env: PluginEnvironment,
): Promise<Router> {
  // Pass the env pieces your app already uses
  // Many Backstage routers need logger and config at minimum
  return await createRouter({
    logger: env.logger,
    config: env.config,
    discovery: env.discovery,
    tokenManager: env.tokenManager,
  });
}

Mount the router in the backend

Copy
// packages/backend/src/index.ts
import app from './app';
import scalr from './plugins/scalr';

// inside the main bootstrap function
const env = useHotMemoize(module, () => createEnv('scalr'));

// register the router at an api path
const scalrRouter = await scalr(env);
apiRouter.use('/scalr', scalrRouter);

Your app structure may differ. Reuse the same env setup pattern you use for other legacy backend plugins

Install the frontend plugin

Copy
yarn workspace app add @scalr-io/backstage-plugin-scalr

Add the plugin to entity pages

Import the component and add a route on the entity page

Copy
// packages/app/src/components/catalog/EntityPage.ts
import React from 'react';
import { EntityLayout } from '@backstage/plugin-catalog';
import { Grid } from '@material-ui/core';
import {
  EntityAboutCard,
  EntityCatalogGraphCard,
  EntityHasSystemsCard,
} from '@backstage/plugin-catalog';
import { EntityScalrEnvironmentContent } from '@scalr-io/backstage-plugin-scalr';

const domainPage = (
  <EntityLayout>
    <EntityLayout.Route path="/" title="Overview">
      <Grid container spacing={3} alignItems="stretch">
        <Grid item md={6}>
          <EntityAboutCard variant="gridItem" />
        </Grid>
        <Grid item md={6} xs={12}>
          <EntityCatalogGraphCard variant="gridItem" height={400} />
        </Grid>
        <Grid item md={6}>
          <EntityHasSystemsCard variant="gridItem" />
        </Grid>
      </Grid>
    </EntityLayout.Route>

    <EntityLayout.Route path="/scalr" title="Scalr">
      <EntityScalrEnvironmentContent />
    </EntityLayout.Route>
  </EntityLayout>
);

export default domainPage;

You can place this on any entity page that fits your setup

Add the modules page route

Import the page component and add a top level route

Copy
// packages/app/src/App.tsx
import React from 'react';
import { Navigate, Route } from 'react-router';
import { FlatRoutes } from '@backstage/core-app-api';
import { CatalogIndexPage } from '@backstage/plugin-catalog';
import { ModulesContent } from '@scalr-io/backstage-plugin-scalr';

export const routes = (
  <FlatRoutes>
    <Route path="/" element={<Navigate to="catalog" />} />
    <Route path="/catalog" element={<CatalogIndexPage />} />
    <Route path="/scalr/modules" element={<ModulesContent />} />
  </FlatRoutes>
);

Import the icon and add a sidebar item that points to the modules page

Copy
// packages/app/src/components/Root/Root.tsx
import React from 'react';
import {
  SidebarItem,
  SidebarDivider,
  SidebarGroup,
  SidebarScrollWrapper,
} from '@backstage/core-components';
import MenuIcon from '@material-ui/icons/Menu';
import HomeIcon from '@material-ui/icons/Home';
import GroupIcon from '@material-ui/icons/Group';
import ExtensionIcon from '@material-ui/icons/Extension';
import LibraryBooks from '@material-ui/icons/LibraryBooks';
import CreateComponentIcon from '@material-ui/icons/AddCircleOutline';
import { ScalrIcon } from '@scalr-io/backstage-plugin-scalr';

export const Root = () => (
  <>
    <SidebarGroup label="Menu" icon={<MenuIcon />}>
      <SidebarItem icon={HomeIcon} to="catalog" text="Home" />
      <SidebarItem icon={ExtensionIcon} to="api-docs" text="APIs" />
      <SidebarItem icon={LibraryBooks} to="docs" text="Docs" />
      <SidebarItem icon={CreateComponentIcon} to="create" text="Create..." />
      <SidebarDivider />
      <SidebarScrollWrapper>
        <SidebarItem
          icon={ScalrIcon}
          to="/scalr/modules"
          text="Scalr Modules"
        />
      </SidebarScrollWrapper>
    </SidebarGroup>
  </>
);

Add backend configuration

Add Scalr settings to your app config. Use your local config file

Copy
# app-config.local.yaml
integrations:
  scalr:
    api-token: ${SCALR_API_TOKEN}
    base-url: ${SCALR_BASE_URL}
    allow-trigger-run: true
    module-namepsaces:
      - id: modns-xxxxxxxxxxx
        display-name: Developer Namespace

API token comes from the Scalr UI under personal access tokens
Base url is the hostname of your Scalr instance
allow trigger run is optional and defaults to false
module namepsaces is optional

Annotate entities to filter data

Filter by environment

Copy
apiVersion: backstage.io/v1alpha1
kind: Domain
metadata:
  name: example
  annotations:
    scalr.com/environment: <ENVIRONMENT_ID>
spec:
  owner: guests

Filter by tag

Copy
apiVersion: backstage.io/v1alpha1
kind: Domain
metadata:
  name: example
  annotations:
    scalr.com/tag: <TAG_NAME>
spec:
  owner: guests

If both annotations exist on the same entity then the tag takes precedence

Changelog

This changelog is produced from commits made to the Scalr plugin since 6 months ago, and based on the code located here. It may not contain information about all commits. Releases and version bumps are intentionally omitted. This changelog is generated by AI.

Breaking changes

  • Trigger runs default to off. Add a new config to enable it. #9 merged 3 months ago

Features

  • Add a config toggle to allow triggering runs. Off by default. #9 merged 3 months ago

Bug fixes

  • Fix error when workspace is empty. #5 merged 4 months ago

Maintenance

  • Switch to npm release. #3 merged 5 months ago

Set up Backstage in minutes with Roadie