Topic: ADR template for Backstage
ADR Template for Backstage — TechDocs Integration, MADR Format, and Catalog Annotations
Backstage is the CNCF-graduated open platform for building developer portals — it's where engineering organizations surface their service catalog, TechDocs documentation, and increasingly, their architecture decision records. The backstage-plugin-adr community plugin renders ADR Markdown files directly on entity pages in Backstage, turning each service's git-stored decisions into a browsable, searchable list alongside its TechDocs. This page explains the MADR template format the plugin expects, how to wire the catalog-info.yaml annotation, and how to fill the hardest ADR section — Considered Options — from the AI chat sessions where you actually deliberated the decision.
TL;DR
Store ADRs as MADR-formatted Markdown files in docs/adr/ inside your component repository. Add backstage.io/adr-location: docs/adr to catalog-info.yaml. The backstage-plugin-adr renders a status-indexed list on the entity's Backstage page. For cross-cutting decisions, use a separate architecture-decisions repository registered as its own Backstage entity. TechDocs renders the full ADR content; the ADR plugin provides the index view. The Considered Options section — the hardest to write — can be pre-filled from ChatGPT or Claude exports via the WhyChose extractor.
Backstage and architecture decision records
Backstage (backstage.io) is a CNCF-graduated developer portal framework open-sourced by Spotify in 2020. It provides three core features relevant to ADR practice:
- Software Catalog: A registry of every service, library, website, and pipeline in the organization. Each entity has a
catalog-info.yamldescriptor file in its repository that Backstage reads to populate the catalog. - TechDocs: A "docs-as-code" documentation system. Markdown files in
docs/are rendered as full documentation pages accessible from the entity page in Backstage. TechDocs uses MkDocs under the hood; amkdocs.ymldefines the nav structure. - Plugin ecosystem: Backstage's functionality is entirely plugin-driven. The
backstage-plugin-adrcommunity plugin — now maintained in the backstage/community-plugins repository — adds dedicated ADR surfacing to the entity page, separate from the general TechDocs documentation view.
The result: with a two-line annotation change in catalog-info.yaml and the plugin installed in your Backstage instance, every service in the catalog surfaces its architecture decision history directly on the entity page. No separate portal, no separate search, no navigation away from the service context where the decision is relevant.
Installing and configuring the backstage-plugin-adr
The ADR plugin ships in two packages from the backstage/community-plugins repository:
@backstage-community/plugin-adr— the frontend plugin installed in your Backstage app'spackages/app/.@backstage-community/plugin-adr-backend— the backend module installed in your Backstage backendpackages/backend/.
Backend setup
In your backend package, add the backend module:
// packages/backend/src/index.ts
backend.add(import('@backstage-community/plugin-adr-backend'));
The backend module handles fetching ADR files from the SCM integrations (GitHub, GitLab, Azure DevOps, Bitbucket) already configured in your Backstage instance. No additional credentials are required beyond what your TechDocs integration uses.
Frontend setup
Add the EntityAdrContent component to the entity page layout in your app. In the component entity page (typically in packages/app/src/components/catalog/EntityPage.tsx), add the ADR tab between TechDocs and any other content tabs:
import { EntityAdrContent, isAdrAvailable } from '@backstage-community/plugin-adr';
// Inside the EntityLayout for components:
<EntityLayout.Route
path="/adrs"
title="ADRs"
if={isAdrAvailable}
>
<EntityAdrContent />
</EntityLayout.Route>
The isAdrAvailable guard hides the ADRs tab for entities that don't have the backstage.io/adr-location annotation — the tab only appears when relevant.
catalog-info.yaml annotation
Add the ADR location annotation to the entity's catalog-info.yaml descriptor file in the component repository:
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: payments-service
description: Async payments processing service
annotations:
backstage.io/techdocs-ref: dir:.
backstage.io/adr-location: docs/adr
spec:
type: service
lifecycle: production
owner: payments-team
The backstage.io/adr-location value is the path to the directory containing ADR Markdown files, relative to the repository root. Common conventions:
docs/adr— the most common path, aligns with TechDocs convention of keeping documentation indocs/architecture/decisions— used by teams that keep architectural artifacts separate from user-facing documentationdoc/adr— theadr-tools(npryce) default; compatible with the plugin
Cross-cutting ADRs in a separate repository
For platform-level or cross-service decisions, register a separate architecture repository as a Backstage entity and point the annotation at it:
annotations:
backstage.io/adr-location: url:https://github.com/my-org/architecture-decisions/tree/main/platform
The full URL form allows the ADR plugin to fetch files from any repository in your SCM integration, not just the entity's own repository. This is the correct approach for a dedicated architecture-decisions monorepo that serves as the organization's ADR library.
The MADR template for Backstage ADRs
The backstage-plugin-adr parses MADR-formatted files most reliably. MADR (Markdown Any Decision Records, developed by Oliver Kopp) uses a consistent frontmatter or heading-level structure that the plugin's index view reads for status, date, and title. The recommended template:
# [Short title — name the decision, not the problem]
* Status: [proposed | rejected | accepted | deprecated | superseded by [ADR-0005](0005-example.md)]
* Deciders: [@alice, @bob, @carol]
* Date: 2026-06-04
* Tags: [payments, infrastructure, security]
Technical Story: [ticket URL or one-sentence description of the triggering event]
---
## Context and Problem Statement
[What situation, constraint, or need led to this decision point? Write for an engineer
who was not in any of the related discussions. Two to four sentences is sufficient.]
## Decision Drivers
* [force 1 — a requirement, constraint, or quality attribute that shaped the decision]
* [force 2]
* [force 3]
## Considered Options
* [Option A — e.g., PostgreSQL]
* [Option B — e.g., MongoDB]
* [Option C — e.g., keep the current SQLite setup]
## Decision Outcome
Chosen option: "[Option A]", because [one-sentence justification connecting the choice
to the Decision Drivers above].
### Positive Consequences
* [what this makes easier — required; an ADR with only upsides is a press release]
### Negative Consequences
* [what this makes harder or what new obligations this creates — at least one required]
---
## Pros and Cons of the Options
### Option A — [name]
[Brief description]
* Good, because [argument a]
* Good, because [argument b]
* Bad, because [argument c — be honest]
### Option B — [name]
[Brief description]
* Good, because [argument d]
* Bad, because [argument e]
* Bad, because [argument f]
### Option C — [name]
[Brief description]
* Good, because [argument g]
* Bad, because [argument h]
Filename convention
Use zero-padded sequential numbers: 0001-use-postgres-for-main-database.md, 0043-switch-payments-to-async-queue.md. The leading number gives the ADR plugin and TechDocs a consistent sort order independent of file modification dates. Use kebab-case for the slug — it becomes the URL in TechDocs and should be readable without decoding. Avoid embedding the decision outcome in the filename (use 0043-payments-queue-architecture.md, not 0043-chose-kafka-for-payments.md) — the filename needs to remain accurate even if the decision is superseded.
Status values the plugin recognizes
The backstage-plugin-adr UI renders colored status badges based on the Status field value. The recognized values and their badges:
| Status value | Badge color | Meaning |
|---|---|---|
proposed |
Yellow | Under review — review window is open |
accepted |
Green | Review closed, decision stands |
rejected |
Red | Decision was formally rejected — useful to preserve reasoning for future reviewers |
deprecated |
Gray | Context became void; no replacement decision exists |
superseded by ADR-XXXX |
Gray (with link) | A newer ADR replaced this one; reference required |
The plugin is case-insensitive for status matching — Accepted, accepted, and ACCEPTED are all recognized. Use lowercase for consistency with MADR tooling.
TechDocs configuration for ADRs
To render ADR files as full TechDocs documentation pages (beyond the plugin's list view), include them in the mkdocs.yml navigation:
site_name: Payments Service Documentation
docs_dir: docs
nav:
- Home: index.md
- Architecture Decisions:
- Overview: adr/index.md
- "0043 — Payments queue architecture": adr/0043-payments-queue-architecture.md
- "0042 — Database selection": adr/0042-database-selection.md
- API Reference: api.md
- Runbooks: runbooks/index.md
Auto-generating the ADR nav
Manually maintaining the nav for each new ADR is tedious. Two approaches to automate it:
- mkdocs-awesome-pages-plugin: A MkDocs plugin that generates nav entries from the file system automatically. Add it to
mkdocs.ymlas a plugin and place a.pagesYAML file indocs/adr/specifying the order (e.g.,order: descto show newest ADRs first). TechDocs with the Tech Docs Builder supports this plugin. - CI-generated nav: A CI step in your PR pipeline that runs a script updating
mkdocs.ymlwhenever a new.mdfile is added todocs/adr/. Combines with the ADR GitHub Action pattern for fully automated ADR lifecycle management.
For most organizations, the awesome-pages approach is simpler and sufficient. The CI-generated nav is worth the investment only when ADR volume is high enough (50+ ADRs in a single repo) that manual nav updates become a real friction point.
Backstage ADRs vs a standalone git repository — when to use each
| Criterion | ADRs in Backstage (TechDocs + plugin) | Standalone git ADR repository |
|---|---|---|
| Discovery surface | Inline on the entity page — engineers find ADRs where they already look up service information | Separate repository — requires knowing where to look |
| Cross-service browsing | Must navigate to each entity individually; no cross-service ADR search out of the box | A single grep across the repository finds all ADRs mentioning a component or decision type |
| CI validation | Available in the component's own pipeline (same as code); can validate ADR format as part of the PR check | Dedicated pipeline per ADR repository; separation of concerns between ADR validation and code validation |
| Non-technical reviewer access | Backstage requires SSO access — standard for engineering orgs, may exclude non-engineering stakeholders | A GitHub repository is accessible to any GitHub user; public repositories are accessible without authentication |
| Decision scope fit | Best for service-scoped decisions (the decision is primarily relevant to engineers working on that service) | Best for cross-cutting platform or organizational decisions |
| History and blame | Full git history in the component repo; blame shows who updated the ADR status | Full git history in the dedicated repo; same capability, separate namespace |
The typical Backstage org ends up with both: service-scoped ADRs in each component repository (surfaced via the plugin), and platform-level ADRs in a central architecture-decisions repository registered as a Backstage entity of kind System or Resource. The plugin annotation on the central entity points at the cross-cutting ADR directory.
ADR governance in Backstage organizations
Most Backstage organizations use GitHub Pull Requests as the ADR review surface — not Teams channels or Jira tickets. The PR-based ceremony for a Backstage ADR:
- Create a branch:
adr/0043-payments-queue-architecture. - Add the MADR file at
docs/adr/0043-payments-queue-architecture.mdwith Status: proposed. - Open a PR against
main. The PR description links to the ADR file. @mention reviewers from dependent services and the platform team. - CI runs: format validation checks that all required MADR fields are present, status vocabulary is valid, and any
superseded byreference resolves to an existing ADR file. - At the agreed review deadline, if no blocking review comment, merge the PR and update Status to accepted in the same commit.
- Backstage TechDocs regenerates on the next build cycle (typically triggered by the main branch push); the ADR plugin refreshes the entity's ADR list.
CI format validation with GitHub Actions
A minimal format check runs in under 30 seconds and catches the most common ADR quality failures:
# .github/workflows/adr-lint.yml
name: ADR format check
on: [pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check required MADR fields
run: |
for f in docs/adr/*.md; do
[ "$f" = "docs/adr/index.md" ] && continue
grep -q "^\* Status:" "$f" || { echo "Missing Status in $f"; exit 1; }
grep -q "^\* Deciders:" "$f" || { echo "Missing Deciders in $f"; exit 1; }
grep -q "^\* Date:" "$f" || { echo "Missing Date in $f"; exit 1; }
grep -q "## Considered Options" "$f" || { echo "Missing Considered Options in $f"; exit 1; }
done
This check prevents the most common ADR quality regression in Backstage organizations: ADRs merged with empty Considered Options sections, which makes the decision appear to be the only option considered and removes the future-engineer context the section is designed to preserve. See the ADR best practices guide for the full rationale on why Considered Options is the section that matters most for long-term decision value.
WhyChose and Backstage ADR practice
The hardest section to write in any MADR-formatted ADR — and the one most often left thin or empty — is Considered Options and its sub-section Pros and Cons of the Options. Engineers writing ADRs in Backstage organizations face the same problem every ADR practitioner faces: the deliberation happened in Claude or ChatGPT sessions, the options were weighed there, the rejection reasons were articulated there. By the time the MADR file is being drafted in a PR, the reasoning is in a chat history that isn't open in the same window as the ADR template.
The WhyChose extractor processes a ChatGPT or Claude export (conversations.json) and surfaces the sessions where architecture alternatives were evaluated. The extracted output includes the Options Considered and the rejection reasons for each non-chosen option — exactly the content that belongs in the MADR Considered Options section. Copy that output into the ADR file before opening the Backstage PR and the hardest section is done.
The workflow that works well in Backstage orgs: deliberate in Claude or ChatGPT (the tools with exportable conversation history), export, run the extractor to surface the decision session, draft the MADR file with the Considered Options section filled from the extractor output, open the PR. The extractor fills the retrospective reasoning; the PR process handles the governance. The result is a Backstage ADR that reads as if you wrote the Considered Options at decision time — because you did, in the AI session.
Further Reading
- MADR ADR template — the full MADR specification: complete template with all optional sections (motivation, links, confirmation mechanism), the rationale for MADR's structure compared to Nygard and arc42, and the tooling ecosystem (adr-tools fork, log4brains support, Backstage plugin compatibility).
- ADR GitHub Action — CI/CD patterns for ADR automation: format validation, status lifecycle enforcement, supersession reference checking, and auto-generation of an ADR index. Applies directly to the PR pipeline for Backstage component repositories.
- ADR template for GitHub — the GitHub-native ADR workflow: PR review ceremonies, branch naming conventions, CODEOWNERS for ADR review routing, and the discussion thread pattern. The governance model that most Backstage organizations follow for ADR review.
- ADR best practices — the quality practices that make ADRs worth reading six months later: writing the Considered Options section as the primary value artifact, the honest-downside requirement, how long each section should be, and the common mistakes that reduce ADR value to zero.
- ADR in a monorepo — how to organize architecture decision records when multiple services share a repository: directory structure patterns, entity-scoped vs repo-scoped ADR organization, and CI validation strategies when ADRs and code live in the same commit.
The Considered Options section is where Backstage ADRs fail
A Backstage ADR with an empty Considered Options section is a press release, not a decision record. The deliberation happened in Claude and ChatGPT — where the alternatives were named, weighed, and rejected with specific reasons. The WhyChose extractor surfaces those sessions and produces structured decision records with the Considered Options section filled. Copy the output into the MADR file before opening the PR.