Rootly is an incident management platform for software teams. It helps you handle on call, coordinate response, and learn from outages. It works in Slack and in a web app.
The Rootly plugin brings that context into Backstage. You can browse services, teams, functionalities, and incidents from Rootly inside your software catalog. Service pages can show a compact card with incidents from the last 30 days plus any active ones. Each entity can get a Rootly tab with richer details on ongoing and past incidents.
The plugin supports multi organization setups. It keeps Rootly and your catalog in sync through annotations and a background processor so Backstage stays the source of truth. PagerDuty service IDs on entities can map to Rootly to keep impact accurate.
Common use cases are simple. Reduce context switching during incidents. Bring service ownership and incident history into one place. Review trends by team or functionality to spot weak points. Keep responders focused on repair instead of chasing links.
Installation Instructions
These instructions apply to self-hosted Backstage only. To use this plugin on Roadie, visit the docs.
Create a Rootly API key
- Sign in to Rootly
- Open Profile then Manage API Keys
- Create a new key
- Copy the key and store it as a secret you can inject as an environment variable in Backstage
Install the packages
yarn add --cwd packages/app @rootly/backstage-plugin
yarn add --cwd packages/backend @rootly/backstage-plugin-entity-processor
Configure app config and proxy
Add a Rootly section and proxy endpoints. Use your environment variables for the tokens.
# app-config.yaml
# Single organization
rootly:
rootly-main:
proxyPath: /rootly/api
# Multi organizations
rootly:
rootly-main:
isDefault: true
proxyPath: /rootly/api
rootly-sandbox:
proxyPath: /rootly-sandbox/api
proxy:
endpoints:
'/rootly/api':
target: 'https://api.rootly.com'
headers:
Authorization: Bearer ${ROOTLY_MAIN_TOKEN}
'/rootly-sandbox/api':
target: 'https://api.rootly.com'
headers:
Authorization: Bearer ${ROOTLY_SANDBOX_TOKEN}
Add the global Rootly page in a classic frontend app
Add a route to the Rootly page.
// packages/app/src/App.tsx
import React from 'react';
import { FlatRoutes, Route } from '@backstage/core-app-api';
import { RootlyPage } from '@rootly/backstage-plugin';
const AppRoutes = () => (
<FlatRoutes>
{/* other routes */}
<Route path="/rootly" element={<RootlyPage />} />
{/* multi organization example */}
<Route
path="/rootly-sandbox"
element={<RootlyPage organizationId="rootly-sandbox" />}
/>
</FlatRoutes>
);
export default AppRoutes;
Add a sidebar link so users can reach the page.
// packages/app/src/components/Root/Root.tsx
import React, { PropsWithChildren } from 'react';
import ExtensionIcon from '@material-ui/icons/ExtensionIcon';
import { SidebarPage, Sidebar, SidebarItem } from '@backstage/core-components';
export const Root = ({ children }: PropsWithChildren<{}>) => (
<SidebarPage>
<Sidebar>
{/* other items */}
<SidebarItem icon={ExtensionIcon} to="rootly" text="Rootly" />
{/* <SidebarItem icon={ExtensionIcon} to="rootly-sandbox" text="Rootly Sandbox" /> */}
</Sidebar>
{children}
</SidebarPage>
);
Add Rootly content to entity pages
Add the overview card to the entity overview.
// packages/app/src/components/catalog/EntityPage.tsx
import React from 'react';
import Grid from '@material-ui/core/Grid';
import { EntitySwitch } from '@backstage/plugin-catalog';
import {
RootlyOverviewCard,
isRootlyAvailable,
} from '@rootly/backstage-plugin';
export const overviewContent = (
<Grid container spacing={3} alignItems="stretch">
{/* other cards */}
<EntitySwitch>
<EntitySwitch.Case if={isRootlyAvailable}>
<Grid item sm={6}>
<RootlyOverviewCard />
</Grid>
</EntitySwitch.Case>
</EntitySwitch>
</Grid>
);
Add the incidents tab to your entity pages.
// packages/app/src/components/catalog/EntityPage.tsx
import React from 'react';
import Grid from '@material-ui/core/Grid';
import { EntityLayout } from '@backstage/plugin-catalog';
import { EntityTechdocsContent } from '@backstage/plugin-techdocs';
import { RootlyIncidentsPage, isRootlyAvailable } from '@rootly/backstage-plugin';
const websiteEntityPage = (
<EntityLayout>
<EntityLayout.Route path="/" title="Overview">
{overviewContent}
</EntityLayout.Route>
<EntityLayout.Route path="/docs" title="Docs">
<EntityTechdocsContent />
</EntityLayout.Route>
<EntityLayout.Route path="/rootly" title="Rootly">
<EntitySwitch.Case if={isRootlyAvailable}>
<Grid item sm={6}>
<RootlyIncidentsPage />
</Grid>
{/* multi organization */}
{/* <Grid item sm={6}>
<RootlyIncidentsPage organizationId="rootly-sandbox" />
</Grid> */}
</EntitySwitch.Case>
</EntityLayout.Route>
</EntityLayout>
);
const serviceEntityPage = (
<EntityLayout>
<EntityLayout.Route path="/" title="Overview">
{overviewContent}
</EntityLayout.Route>
<EntityLayout.Route path="/docs" title="Docs">
<EntityTechdocsContent />
</EntityLayout.Route>
<EntityLayout.Route path="/rootly" title="Rootly">
<RootlyIncidentsPage />
{/* multi organization */}
{/* <Grid item sm={6}>
<RootlyIncidentsPage organizationId="rootly-sandbox" />
</Grid> */}
</EntityLayout.Route>
</EntityLayout>
);
If you add Rootly content to a system page, add the organization annotation to all components in that system when using multi organizations.
// packages/app/src/components/catalog/EntityPage.tsx
import React from 'react';
import { EntityLayout } from '@backstage/plugin-catalog';
import { RootlyIncidentsPage } from '@rootly/backstage-plugin';
const systemPage = (
<EntityLayout>
<EntityLayout.Route path="/" title="Overview">
{/* your overview content */}
</EntityLayout.Route>
<EntityLayout.Route path="/rootly" title="Rootly">
{/* Make sure to add rootly.com/organization-id on all components in the system when using multi organizations */}
<RootlyIncidentsPage />
</EntityLayout.Route>
</EntityLayout>
);
Add Rootly annotations to your entities
Rootly reads these annotations on entities in your catalog. Use id or slug for a given item. Not both.
# catalog-info.yaml metadata.annotations
rootly.com/organization-id: rootly
rootly.com/service-id: 7a328a08-6701-445e-a1ad-ca2fb913ed1e
rootly.com/service-name: ElastiSearch Staging
rootly.com/service-slug: elasticsearch-staging
rootly.com/service-auto-import: enabled
rootly.com/functionality-id: 7a328a08-694f4e1b-abbc-4cf7-bba0-a403df30ed88
rootly.com/functionality-name: Login
rootly.com/functionality-slug: login
rootly.com/functionality-auto-import: enabled
rootly.com/team-id: 39e77dcc-e056-4849-9dda-a362b2413e5c
rootly.com/team-slug: infrastucture
rootly.com/team-name: Infrastucture
rootly.com/team-auto-import: enabled
Example entity with a Rootly service and a PagerDuty service id annotation. Backstage remains the source of truth. The processor syncs the PagerDuty service id to Rootly.
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: elasticsearch-staging
description: |
elasticsearch-staging
annotations:
github.com/project-slug: backstage/backstage
backstage.io/techdocs-ref: dir:.
lighthouse.com/website-url: https://rootly.com
rootly.com/service-slug: elasticsearch-staging
pagerduty.com/service-id: <sample-service-service-id>
spec:
type: grpc
owner: guests
lifecycle: experimental
Configure the backend entity processor new backend system
Enable the Rootly entity processor module. This keeps Rootly in sync with your catalog.
// packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';
const backend = createBackend();
// other backend modules
backend.add(import('@rootly/backstage-plugin-entity-processor'));
backend.start();
Configure the backend entity processor legacy backend system
The repository documents the processor as a module for the new backend system. It does not include legacy backend setup steps. The frontend parts in this guide work on legacy. The background sync that the processor provides is documented for the new backend only.
Use the new frontend system optional
If your app uses the new frontend system you can auto discover the plugin by config or enable it in code.
Enable by config for all plugins or only Rootly.
# app-config.yaml
app:
packages: 'all'
---
app:
packages:
include:
- '@rootly/backstage-plugin'
Enable by code.
// packages/app/src/App.tsx
import { createApp } from '@backstage/frontend-defaults';
import rootlyPlugin from '@rootly/backstage-plugin/alpha';
const app = createApp({
features: [
rootlyPlugin,
],
});
export default app.createRoot();
Optional extension customization examples.
# app-config.yaml
app:
extensions:
- 'page:rootly':
config:
path: '/incidents'
- 'nav-item:rootly': false
- 'nav-item:rootly':
config:
title: 'Incidents'
- 'entity-card:rootly': false
- 'entity-content:rootly': false
- 'entity-content:rootly':
config:
path: '/incidents'
title: 'incidents'
Things to Know
Creating a Rootly API key
Because of the features provided by the plugin, an API key with full access to your Rootly domain is required.
- Read access on services is needed to list services, write access to link entities to services.
- Read access on incidents needed to list incidents.
-
Go to
Profile
->Manage API Keys
. -
Click on
Generate New API Key
button. -
Copy the key.
Screenshots
RootlyPage
component
RootlyOverviewCard
component
RootlyIncidentsPage
component
Changelog
This changelog is produced from commits made to the Rootly plugin since a year 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
None
Features
- Migrate the Rootly plugin to the new Backstage frontend system with an alpha subpath export. Legacy exports remain unchanged. No visual changes. Supports feature discovery or manual enable. Includes page nav item entity card entity tab extensions with config options. #51 3 weeks ago
Set up Backstage in minutes with Roadie
Focus on using Backstage, rather than building and maintaining it.