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.

Step 1: Install the frontend package

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

Step 2: Add the ADR tab to your entity page

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

const serviceEntityPage = (
  <EntityLayout>
    {/* other tabs */}
    <EntityLayout.Route if={isAdrAvailable} path="/adrs" title="ADRs">
      <EntityAdrContent />
    </EntityLayout.Route>
  </EntityLayout>
);

export default serviceEntityPage;

Step 3: Annotate your entities with the ADR location

Copy
# catalog-info.yaml
metadata:
  annotations:
    backstage.io/adr-location: docs/adrs

The value can be a path relative to this catalog file or an absolute URL to the directory that holds the ADR markdown files.

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

Step 4: Configure integrations in app config

Add the source control hosts you read ADRs from. Here is a simple GitHub entry. Adjust for your hosts.

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

Step 5a: Install the backend plugin (legacy backend)

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

Create the backend 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 it into the backend

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

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

These legacy backend steps come from the backend plugin guide.

Step 5b: Install the backend plugin (new backend system)

If your app uses the new backend system do this instead of Step 5.

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

Register the feature in the backend

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

const backend = createBackend();

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

// other features
backend.start();

Step 7: Add ADRs to search results optional

Install the ADR search backend module

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

Register it in the new backend system along with search

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

const backend = createBackend();

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

backend.start();

You can tune the index schedule under the key search.collators.adr in app config if needed. These steps come from the search module guide.

Step 8: Show ADR results on the search page (optional)

Copy
// packages/app/src/components/search/SearchPage.tsx
import React from 'react';
import { SearchResult } from '@backstage/plugin-search';
import { List } from '@material-ui/core';
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}
                />
              );
            default:
              return null;
          }
        })}
      </List>
    )}
  </SearchResult>
);

Step 9: Custom ADR filename filter (optional)

If you need to skip template files or enforce a naming scheme add a filter in the entity content

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

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

<EntityLayout.Route if={isAdrAvailable} path="/adrs" title="ADRs">
  <EntityAdrContent filePathFilterFn={myCustomFilterFn} />
</EntityLayout.Route>;

Step 10: Custom content decorators (Optional)

You can modify how ADR markdown is rendered while keeping default behavior

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

const myCustomDecorator: AdrContentDecorator = ({ content, filename }) => {
  if (filename?.includes('security')) {
    return { content: `<!-- masked -->\n${content}` };
  }
  return { content };
};

<EntityLayout.Route if={isAdrAvailable} path="/adrs" title="ADRs">
  <EntityAdrContent
    contentDecorators={[
      AdrReader.decorators.createRewriteRelativeLinksDecorator(),
      AdrReader.decorators.createRewriteRelativeEmbedsDecorator(),
      AdrReader.decorators.createFrontMatterFormatterDecorator(),
      myCustomDecorator,
    ]}
  />
</EntityLayout.Route>;

Changelog

This changelog is produced from commits made to the Architecture Decision Records 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

  • 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