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
yarn workspace backend add @scalr-io/backstage-plugin-scalr-backend
New backend
Add the plugin module to your backend
// 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
// 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
// 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
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
// 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
// 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>
);
Add a sidebar link
Import the icon and add a sidebar item that points to the modules page
// 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
# 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
apiVersion: backstage.io/v1alpha1
kind: Domain
metadata:
name: example
annotations:
scalr.com/environment: <ENVIRONMENT_ID>
spec:
owner: guests
Filter by tag
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
Focus on using Backstage, rather than building and maintaining it.