In this guide
Six months after a big technical decision, someone always asks why. Why Postgres and not DynamoDB? Why is this a separate service? Why do we use a queue here instead of a direct call? The person who made the call has moved teams, the Slack thread has scrolled out of reach, and the meeting notes are in a document nobody can find. The team either reverses a good decision for bad reasons, or keeps a bad decision because nobody dares touch it.
An architecture decision record (ADR) is a short document that captures one decision, the context that led to it, and its consequences. The idea is simple, and many teams try it. Most ADR collections still die after a handful of entries. This guide covers what is worth recording, a template that stays short, the tooling, a review workflow, and the part most guides skip: connecting decisions to the code they govern so people actually find them.
Key takeaways
- An ADR records one decision: the context, the decision, and its consequences. It is not a design document or a spec.
- Write an ADR when a decision is expensive to reverse, affects more than one team or module, or will surprise someone later.
- Keep ADRs in the repository as numbered Markdown files, reviewed in pull requests like code.
- ADRs are immutable once accepted. To change a decision, write a new ADR that supersedes the old one.
- Link ADRs from the code, config, lint rules and diagrams they govern. An ADR nobody finds is an ADR nobody reads.
- Aim for one page. If the context section needs five pages, link to a design document instead.
What an ADR is (and is not)
Michael Nygard described the format in 2011 in a short post titled "Documenting Architecture Decisions". His original template has five parts: title, status, context, decision, and consequences. Almost every later template is a variation on it.
An ADR is:
- Small. One decision per record, usually one page.
- Dated and numbered.
0007-use-postgres-for-orders.md. - Immutable after acceptance. History is preserved by superseding, not editing.
- Written for a future reader who has none of today's context.
An ADR is not:
- A design document. A design doc explores options in depth before a decision; an ADR records the outcome and links to the design doc if there is one.
- A spec or ticket. It does not describe how to implement the decision step by step.
- Meeting minutes. Who said what does not matter; what was decided and why does.
When to write one
Writing an ADR for every choice produces noise nobody reads. Writing none leaves the important decisions undocumented. A practical test is to write an ADR when at least one of these is true:
- The decision is expensive to reverse. Database choice, language or framework, service boundaries, authentication approach, public API style, data retention model.
- It affects more than one team or module. Shared libraries, cross-service protocols, monorepo tooling, CI conventions.
- It will surprise someone. "We deliberately do not use an ORM here." "This service is allowed to read the orders table directly." Anything a reasonable newcomer would try to "fix".
- You considered real alternatives. If there was a genuine trade-off, record it before the reasoning is forgotten.
- It changes a boundary or a rule. If you are adding or removing a lint rule that enforces architecture, the reason belongs in an ADR.
Skip ADRs for choices that are cheap to change, local to one file, or already dictated by a team standard.
A template that stays short
Nygard's format, plus two additions that make ADRs more useful in practice (alternatives considered, and links to code):
# 7. Use PostgreSQL for the orders service
Date: 2026-03-14
Status: Accepted
Deciders: orders team, platform team
Supersedes: -
Superseded by: -
## Context
Orders need multi-row transactions (order, line items, inventory reservation) and
ad hoc reporting queries from support. Expected volume is under 2,000 writes per
second for the next two years. The team already operates PostgreSQL for two other
services and has on-call experience with it.
## Decision
We will store orders in PostgreSQL 16 on the managed database service, one database
per service, accessed only by the orders service.
## Alternatives considered
- DynamoDB: scales further, but multi-item transactions and ad hoc queries are
harder, and nobody on the team has operated it.
- Shared database with the catalog service: fewer instances, but couples the
two services' schemas and deploys.
## Consequences
- Support reporting can use read replicas instead of a separate pipeline.
- Other services must use the orders API, not the database. Enforced by
dependency rule `orders-db-private` in `.dependency-cruiser.js`.
- If write volume grows past roughly 10x, we will need to revisit partitioning.
## Links
- Code: `services/orders/internal/store/`
- Design doc: docs/design/orders-storage.md
- Related: ADR 3 (one database per service)
The Consequences section is the most important and most often skipped. List the good and the bad. A decision with no downsides listed was not thought through, or the downsides were left out on purpose.
The Links section is what keeps the ADR connected to reality, and it is covered in more detail below.
Status values
Keep the set small: Proposed, Accepted, Rejected, Deprecated, Superseded by N. A rejected ADR is worth keeping. It answers "did anyone consider X?" with "yes, here is why we did not".
Other templates
If you want more structure, MADR (Markdown Any Decision Records) adds sections for decision drivers and a pros-and-cons list per option, and its format is well documented. Y-statements compress a decision into one sentence: "In the context of X, facing Y, we decided for Z and against W, to achieve Q, accepting downside D." Y-statements are a good summary line to put at the top of a longer ADR.
Tooling
You do not need tooling. A folder of Markdown files and a naming convention are enough. That said, a few tools remove friction:
adr-tools (Nat Pryce's shell scripts) creates and links records:
brew install adr-tools
adr init docs/adr # creates docs/adr and ADR 1 "Record architecture decisions"
adr new "Use PostgreSQL for the orders service"
adr new -s 7 "Move orders to partitioned tables" # supersedes ADR 7 and updates both files
adr link 9 "Amends" 4 "Amended by"
adr generate toc > docs/adr/README.md
log4brains builds a searchable static site from an ADR folder and supports MADR, which helps once you have more than a few dozen records.
Whatever you use, keep the files in the repository. ADRs in a wiki drift from the code exactly the way architecture diagrams do.
Where to put the folder
- Single repo, single team:
docs/adr/at the root. - Monorepo with several teams: a root
docs/adr/for cross-cutting decisions, plusservices/<name>/docs/adr/for decisions local to one service. - Many repos: keep service-level ADRs in each repo, and cross-cutting ones in a dedicated architecture repo that every team can find.
A review workflow that works
Treat an ADR like code: propose it in a pull request.
- Draft the ADR with status Proposed, in a branch.
- Open a pull request and request review from the people affected, typically code owners of the modules involved. Add
docs/adr/toCODEOWNERSso an architecture group or tech leads are included automatically. - Discuss in the PR. Comments are anchored to specific lines, and the history is preserved with the repository.
- Merge with status Accepted, or close with status Rejected (and consider merging the rejected record anyway).
- Link it from the relevant code and docs in the same or a follow-up PR.
Set a time box. A proposal that sits open for a month is a decision by default. If consensus is not forming in a week or two, the deciders named in the ADR make the call and record the dissent in the consequences.
Linking decisions to code
This is the step that determines whether ADRs are read. People do not browse an ADR folder. They open a file, wonder why it is the way it is, and either find the answer in a few seconds or give up. Put the answer where they will look.
Comments at the point of surprise.
// Orders are stored in PostgreSQL, accessed only by this package.
// See docs/adr/0007-use-postgres-for-orders.md before adding another reader.
package store
Lint and architecture rules. If a dependency rule enforces the decision, reference the ADR in its message, so the developer who breaks it is sent straight to the reasoning:
{
name: 'orders-db-private',
comment: 'Only the orders service may use its database. See docs/adr/0007-use-postgres-for-orders.md',
severity: 'error',
from: { pathNot: '^services/orders/' },
to: { path: '^services/orders/internal/store/' },
}
The module boundaries and coupling post covers these rules in detail.
Module READMEs. A short README.md in a service or package folder with a "Decisions" list linking the relevant ADRs.
Diagrams. Arrows and boxes that exist because of a decision should name it. "Order events (ADR 12)" on an arrow is enough.
Pull request templates. Add a line: "Does this change contradict or require an ADR?" It costs reviewers two seconds and catches the decisions that would otherwise be made silently in a diff.
Agent instruction files. Coding agents read CLAUDE.md or AGENTS.md before working. Listing the few ADRs that constrain the codebase ("never add a second reader of the orders database; see ADR 7") is one of the cheapest ways to stop an agent from undoing a decision it has no context for. See human-in-the-loop for AI coding agents for how this fits into review.
Keeping the collection alive
- Start with one. Write ADR 1 ("Record architecture decisions") and ADR 2 about the most recent significant decision. Backfill two or three historical decisions people keep asking about, marked as recorded after the fact.
- Make it the default answer. When someone asks "why" in chat and the answer is a decision, reply with a link to the ADR, or write one.
- Review in retros. Once a quarter, list ADRs whose consequences turned out differently than expected. Some need superseding.
- Never edit an accepted decision's content. Fix typos, add links, update status. If the decision changes, supersede.
Finding the code a decision affects
The hardest part of linking ADRs to code is knowing which code is affected, especially for decisions made years ago. A code map helps: OpenVisio's Explore view shows which modules and files depend on the area a decision governs, and in Ask you can ask a question like "what reads from the orders database?" and get an answer with the relevant code paths. Use that to fill in the Links section with real paths, and to check, when superseding an old decision, how much of the codebase still assumes it.

