Architecture Decision Records logo

Backstage Architecture Decision Records Plugin

Created by Phil Kuang

Architecture Decision Records are short documents that capture important engineering choices. They explain the context, the options you considered, the decision you made, and the consequences. ADRs help future maintainers understand why the system looks the way it does.

The ADR plugin brings those records into Backstage so they sit next to the services they affect. It adds an entity view that lists ADR files from your repo and shows title, status, and date. You can open each ADR as rendered markdown. Relative links and images are rewritten so diagrams and references load as expected. The plugin understands common MADR formats version 2 and version 3. You can tailor file selection and content formatting if your team uses a different template.

Search is built in. Engineers can find ADRs across the catalog and jump straight to the service that owns them. This makes onboarding easier, speeds up reviews, and gives teams a clear history for audits. The plugin works with multiple code hosts through Backstage integrations, and it can serve images from private repos through the backend so content stays accessible.

Installation Instructions

These instructions apply to self-hosted Backstage only. To use this plugin on Roadie, visit the docs.

Install the frontend plugin

Copy
# from your Backstage root
yarn --cwd packages/app add @backstage-community/plugin-adr

Add the ADR tab to Entity pages

Copy
// packages/app/src/components/catalog/EntityPage.tsx
import React from 'react';
import { EntityLayout } from '@backstage/plugin-catalog';
import { EntityAdrContent, isAdrAvailable } from '@backstage-community/plugin-adr';

// add to each entity page layout you use
export const serviceEntityPage = (
  <EntityLayout>
    {/* other tabs */}
    <EntityLayout.Route if={isAdrAvailable} path="/adrs" title="ADRs">
      <EntityAdrContent />
    </EntityLayout.Route>
  </EntityLayout>
);
Copy
// packages/app/src/components/search/SearchPage.tsx
import React from 'react';
import { List } from '@material-ui/core';
import { SearchResult } from '@backstage/plugin-search-react';
import { AdrSearchResultListItem } from '@backstage-community/plugin-adr';
import { AdrDocument } from '@backstage-community/plugin-adr-common';

export const SearchPage = () => (
  <SearchResult>
    {({ results }) => (
      <List>
        {results.map(({ type, document, highlight, rank }) => {
          switch (type) {
            case 'adr':
              return (
                <AdrSearchResultListItem
                  key={document.location}
                  result={document as AdrDocument}
                  highlight={highlight}
                  rank={rank}
                />
              );
            default:
              return null;
          }
        })}
      </List>
    )}
  </SearchResult>
);

Install the backend plugin for the old backend system

Copy
# from your Backstage root
yarn --cwd packages/backend add @backstage-community/plugin-adr-backend

Create the plugin router

Copy
// packages/backend/src/plugins/adr.ts
import { createRouter } from '@backstage-community/plugin-adr-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';

export default async function createPlugin(
  env: PluginEnvironment,
): Promise<Router> {
  return await createRouter({
    reader: env.reader,
    cacheClient: env.cache.getClient(),
    logger: env.logger,
  });
}

Wire the router into the backend

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

// inside main
const adrEnv = useHotMemoize(module, () => createEnv('adr'));
apiRouter.use('/adr', await adr(adrEnv));

Install the backend plugin for the new backend system

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

const backend = createBackend();

// add the ADR backend feature
backend.add(import('@backstage-community/plugin-adr-backend'));

// other features

backend.start();

Configure integrations

Add your source control hosts under integrations in app config. Here is a GitHub example.

Copy
# app-config.yaml
integrations:
  github:
    - host: github.com
      token: ${GITHUB_TOKEN}

Annotate entities with the ADR location

Add the annotation to each entity that has ADRs.

Copy
# catalog-info.yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: my-service
  annotations:
    backstage.io/adr-location: docs/adrs
spec:
  type: service
  owner: team-a
  lifecycle: production

The path is relative to the catalog file. You can use an absolute URL if you store ADRs outside the repo.

Example layout

Copy
repo-root
  catalog-info.yaml
  docs
    adrs
      0001-use-adrs.md
      0002-use-cloud.md

Add ADRs to Search with the new backend system

Install the ADR search backend module.

Copy
# from your Backstage root
yarn --cwd packages/backend add @backstage-community/search-backend-module-adr

Register search backend and the ADR collator.

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

const backend = createBackend();

// search backend
backend.add(import('@backstage/plugin-search-backend/alpha'));

// ADR collator module
backend.add(import('@backstage-community/search-backend-module-adr'));

backend.start();

Optional config for the collator

Copy
# app-config.yaml
search:
  collators:
    adr:
      schedule:
        frequency: { minutes: 30 }
        timeout: { minutes: 10 }

Optional frontend tweaks

You can filter which files show up. You can also decorate the rendered content.

Copy
// packages/app/src/components/catalog/EntityPage.tsx
import React from 'react';
import { EntityLayout } from '@backstage/plugin-catalog';
import {
  EntityAdrContent,
} from '@backstage-community/plugin-adr';
import { AdrFilePathFilterFn } from '@backstage-community/plugin-adr-common';

const myFilter: AdrFilePathFilterFn = path => {
  if (path === '0000-adr-template.md') return false;
  return /^(decided-adrs\/)?\d{4}-.+\.md$/.test(path);
};

export const serviceEntityPage = (
  <EntityLayout>
    <EntityLayout.Route path="/adrs" title="ADRs">
      <EntityAdrContent filePathFilterFn={myFilter} />
    </EntityLayout.Route>
  </EntityLayout>
);

Use content decorators if you need to transform markdown before rendering.

Copy
// packages/app/src/components/catalog/EntityPage.tsx
import React from 'react';
import { EntityLayout } from '@backstage/plugin-catalog';
import {
  EntityAdrContent,
  AdrReader,
} from '@backstage-community/plugin-adr';

const myDecorator = ({ content, filename }: { content: string; filename?: string }) => {
  if (filename?.includes('security')) {
    return { content: `> Security review required\n\n${content}` };
  }
  return { content };
};

export const serviceEntityPage = (
  <EntityLayout>
    <EntityLayout.Route path="/adrs" title="ADRs">
      <EntityAdrContent
        contentDecorators={[
          AdrReader.decorators.createRewriteRelativeLinksDecorator(),
          AdrReader.decorators.createRewriteRelativeEmbedsDecorator(),
          AdrReader.decorators.createFrontMatterFormatterDecorator(),
          myDecorator,
        ]}
      />
    </EntityLayout.Route>
  </EntityLayout>
);

Changelog

This changelog is produced from commits made to the Architecture Decision Records plugin since a year ago. 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

  • Add filename context to AdrContentDecorator. Decorators can read the ADR file name
    #5486 merged 12 days ago
  • Add backend extension point to register a custom ADR parser. Support formats beyond MADR such as Nygard
    #3475 merged 6 months ago
  • Hide frontend entity content when the ADR annotation is missing. Use the new frontend system filter
    #2020 merged 10 months ago

Bug fixes

  • Return other ADRs even if one file fails to parse. Log the bad file
    #2612 merged 8 months ago
  • Fix backend plugin API import
    #2418 merged 9 months ago

Maintenance

  • Remove unused dev dependency canvas
    #3565 merged 5 months ago
  • Update repo tools to cut knip false positives
    #3018 merged 6 months ago
  • Remove usages of backend tasks in ADR
    #2355 merged 9 months ago
  • Remove an unused backend common dependency
    #1826 merged 10 months ago

Set up Backstage in minutes with Roadie