Appearance
Architecture Decision Records
What an ADR is for, what goes in one, and what has to stay out for the record to keep its value
Related Concepts: Postmortems | Module Decomposition | Database Safety Under Continuous Deployment
An architecture decision record (ADR) is a short document that captures one decision the team made, the reasoning behind it, and what the team accepted by making it. It lives in the repository it applies to, under docs/adr/, beside the code that carries the decision out. Each record answers the question a future reader asks when they look at the code: why is this done this way?
ADRs exist because reasoning disappears faster than code. The code that implements a decision stays in the repository indefinitely. The conversation that produced it lives in a pull request thread, a meeting, or somebody's memory, and all of those fade. Within a few months the team is left with a design and no account of the alternatives it beat or the constraint that forced it. Michael Nygard, who proposed the format in 2011, described the two outcomes: a later engineer either accepts the decision blindly, or reverses it without understanding its consequences. The record is what gives them a third option, which is to judge whether the decision still holds.
Where ADRs Sit
Three bodies of writing carry a project's intent, and an ADR has a specific place among them.
- The standards site, this documentation, holds the concepts and implementation guides that apply across every project. A decision that is already a Synapse standard needs no ADR. The ADR is for the decision this project made within or beyond those standards.
- ADRs hold the architectural decisions local to one repository: what a module owns, where a boundary sits, which approach was chosen for a cross-cutting problem and what it was chosen over.
- Epics, issues, and pull requests hold the story of the work itself. The pull request description records the change-level narrative: what moved, what was tried and set aside, and the constraint that shaped the result.
The line between the last two is the one that gets crossed most often. ADRs record the large architectural decisions. Pull request descriptions record the smaller, change-level story of why the code took its shape. An account of what one story built belongs in that story's pull request, which already exists. Putting it in an ADR gives it an ADR number without giving it a decision.
Research documents in docs/research/ feed ADRs. A long-form alternatives analysis does most of the thinking and the citing. The ADR distills the decision and the reasoning from it and links back.
When to Write One
The threshold is any decision you would otherwise have to re-explain the next time it comes up. Three tests catch most of them:
- A future engineer will look at the code and wonder why it is done this way.
- The answer to a design question depends on a constraint the code cannot make obvious: a customer's identity provider holds a URL, a vendor zeroes usage data after a month, a migration runner wraps every file in one transaction.
- One option was explicitly chosen over a reasonable alternative.
The third test is the definition. If no alternative was weighed, there was no decision, and the record has nothing to hold. A record that describes what was built and names nothing it was built in place of is a pull request description.
Decisions that tend to pass the tests fall into a few families: structure (which modules exist and what each owns), non-functional requirements (how security, availability, or cost shaped a design), dependencies and interfaces (what one part may assume about another, what a published contract promises), and construction techniques (a library, tool, or process the project committed to). A decision in any of these families that is costly to undo deserves a record.
Three kinds of decision do not need one. A decision that is easy to reverse carries little reasoning worth preserving. A decision that follows an established pattern in the repository is already recorded by the pattern's own ADR. A decision dictated by an external requirement has its reasoning in the requirement.
Write more records than feels natural. Most directories are underdocumented relative to the value a record carries the first time it saves someone a re-derivation. When in doubt about whether a decision is worth a record, write it. The bar for what counts as a decision stays where it was, and the rest of this article is mostly about holding that bar.
Required Fields
Format is mostly up to the team. Two fields are required regardless of format, and two more are expected.
Date. The day the decision was made, or finalized after discussion. This is a different day from the day the ADR was written, and a record written after the fact carries the decision date. Records accumulate, and a reader who finds two that touch related topics needs to know which came first.
Scope. Most of our projects are monorepos, where one repository holds several services with different concerns. One repository has one docs/adr/ directory, and one record rarely applies to everything in it. The scope field declares what the record governs. Values depend on the project: a service name such as backend or organization-ui, a group such as react apps or backends, or application for the whole repository. Scope is what lets a reader working on the backend skip the frontend-only decisions, and the reverse.
Status. One of a short fixed list the repository's ADR README defines: Proposed, Accepted, Rejected, Deprecated, or Superseded by a named record. The README's index table and the record's own status say the same thing. A status that reads "Accepted" on a record whose text says its conclusion was reversed is a mistake, and one that gets fixed in place.
Title. The title states the decision as a sentence a reader can agree or disagree with. "A Job's Usage Outlives the Job" and "Accepting an Invitation Is Not Verifying an Email" are decisions. "Usage Tracking" and "Invitation Handling" are topics, and a topic title gives the reader nothing until they open the file.
The Body
Beyond the required fields, a serviceable ADR has four parts. Nygard's original template, MADR, and three paragraphs of prose all cover them, and the form matters less than the content.
Context
The situation that forced the decision. The context gives the facts a reader needs to judge whether the decision still holds: what was true about the system, what constraint applied, what problem surfaced. It is written for a reader who arrives later and lacks those facts, so it states them as facts, without arguing for the outcome. A context section that names the issue and the epic links the record to the trail without repeating it.
Decision
What we are going to do, written as rules that hold after a rename: what a module owns, what a caller may assume, what is refused and why. A later section covers this at length because it is the part that most often goes wrong.
Why
The reasoning, the alternatives considered, and the trade-off accepted. This section is the one the record exists for. Each alternative gets a sentence or two on why it lost, and when there was only one alternative an inline sentence rejecting it is enough. An alternative that could never have worked in context is padding, and a reader who notices one trusts the rest of the section less.
The trade-off is stated plainly: what got harder, what is now slower, what is now a constraint on later work. A Why section that lists only what the decision gains is an advertisement. A record with a Decision and no Why is a notice, and a notice survives only until the first person who disagrees with it.
Consequences
What becomes easier or harder because of the decision. Consequences are the part of the record most likely to be read by someone deciding whether to change it, so they are candid, and the negative ones are the ones worth the most care. A cost written down at decision time protects the decision from being reversed later for a cost everyone already accepted. A cost left out reads later as a cost nobody saw.
When something enforces the decision, the Consequences section says what. A dependency rule, a lint rule, a test, or a review step that catches violations is a consequence of the decision, and naming it tells a reader what will push back when they stray.
Writing a Decision That Survives a Rename
An ADR explains why a class of decision was made. The code explains how a specific implementation works. The "how" belongs in the code; the "why" belongs in the ADR. Keeping them apart is what lets a record stay true while the code under it moves.
The practical test is to strip every named function, file, class, route, column, and migration out of the Decision section and read what is left. If a rule remains that would still hold after the whole module was reimplemented, the record holds a decision. If nothing remains, the record is a description of the current code, and it will be wrong as soon as the code changes.
Two passages recording the same decision show the difference. The first is written in terms of the implementation.
JobPurgePort.purge(target, usageAtDeletion)writes the record inside the purge transaction, before its deletes, and setsdeleted_at. Each running total takes the greater of the record's value and the deleter's. Every conflict update onjob_usagerequiresdeleted_at IS NULL, so a worker that saves a job after its deletion leaves the record alone.
The second is written as a rule.
Deleting a job records its usage at that moment and freezes the record. A worker that saves more after the deleter read the job keeps its figures, and a job saved after its deletion leaves the frozen record alone, so the reports never count a job twice.
The second passage survives a rename of the port, the column, and the table. It also reads faster, because it states the rule and the reader never has to infer it from the mechanism. The mechanism is in the code, and anyone who needs it can open the file.
Concrete names are allowed in one place: a short illustration that names one, once, to make a principle tangible. The same applies to code. A small example that illustrates a concept is fine. A wall of code is almost always wrong, because an ADR is a strategic document and code is tactical.
Facts that the code already states are a related hazard. A context that says a public service takes ten use cases, or that twelve tools serve an endpoint, is a count the code will contradict within weeks. State the shape ("the service exposes one read per consumer") and leave the count to the code.
What an ADR Leaves Out
An audit of one project's directory, run in October 2026, found that most of what was wrong with its records was content that should never have entered them. Each item below names the content, why it hurts, and where it belongs.
An implementation plan. Phases, steps, and effort estimates are planning artifacts written before the work exists. Once the work ships, the plan is either carried out or abandoned, and either way it describes nothing current. The plan belongs in the issue or the pull request. When an ADR is drafted before implementation, the plan is removed when the record is accepted.
Open questions. Questions written at decision time are answered by the code that ships, and a record that keeps them reads as undecided long after it was settled. One record kept five open questions for a year after each had been answered, including a question about an encryption approach that a later record decided in full. An open question that still matters is an issue. A question the code answered is deleted.
A build log. Lists of what each pull request added, test counts, checkmarks beside completed phases, achievements, and refinements are a narrative of the work. The pull request trail holds that narrative already, with the diffs attached. One record carried a seven-phase migration strategy, an implementation summary, and a test count, and several of the entries described code that had since been removed.
A future enhancements list. Work that might happen later is a backlog, and the backlog is the issue tracker. A list of enhancements in an ADR either goes stale as the items ship or sits as a claim the record makes about the future with no one accountable for it.
A per-story amendment. This is the most common failure and the most damaging. A story touches the area an ADR covers, and the author appends a dated section recording what the story built: the migration number, the renamed entity, the new route, the column that became nullable. The decision has not changed. The record has gained a paragraph of implementation detail that will be wrong after the next rename, and the next story adds another. One record accumulated twenty dated amendments and ran to nearly three hundred lines around a decision of about seventy. Another carried eleven "Amended by" paragraphs below sentences that still stated the original rule, so the text contradicted itself. The story-level history belongs in the story's pull request. A later story that changes the decision is a new record, as the next section describes.
A deployment procedure. The order two pull requests deploy in, a sync statement that is safe to run twice, the lock a migration takes, and the timestamp conversion a report applies are the mechanics of one change. They are exactly what a pull request description is for. They are also the content most likely to be mistaken for a decision, because they are reasoned and specific. The strip-the-names test applies: a procedure that holds for one deploy is part of that deploy's story.
More than one decision. A record that holds eleven numbered decisions for one story, including a port rename and a CI script, is a pull request description with headings. A record holds one decision. Where a story made several, the ones that were choices among alternatives each get a record, and the rest go in the pull request. One decision per record is also what lets each one be superseded on its own.
A decision reversed before it mattered. A decision overturned within days, before anything outside its own pull request depended on it, is a rejected alternative of the decision that replaced it. It is folded into that record's Why section. A record that lived long enough for code or other records to cite it stays, marked superseded.
A decision the standards site already made. A record saying the project uses clean architecture, or the repository pattern, restates a Synapse standard. The ADR is for what this project decided within or beyond the standard, such as which module owns a particular table, or why this project deviates.
Changing a Record
An accepted ADR is immutable with respect to its decision. The record says what was decided on its date, and that fact stays true forever. The question any edit has to answer is whether it changes what was decided.
Changing our mind is a new record. When the decision changes, write a new ADR and mark the old one superseded by it. The new record's context explains why the old one no longer applies. The old record keeps its text, so a reader can see what was decided then and why. One supersession, one line in each status, and the directory's index agrees with both. A record that supersedes a chain of earlier records collapses the chain: a sequence of five records written over five days, each amending its predecessors, holds one or two decisions, and the new record keeps those.
Fixing a mistake is an edit in place. A typo, an unclear sentence, a wrong cross-reference, a status that disagrees with the text, or a fact that was already wrong on the day the record was written are all mistakes in the record, and the decision is untouched by correcting them. Rewriting a Decision section so it states the same decision in terms that survive a rename is the same kind of fix. These edits keep the record's date, because the decision did not move.
The test is the strip-the-names test again, applied to the diff. An edit that leaves the rules unchanged is a fix. An edit that changes a rule, adds one, or removes one is a change of mind, and it belongs in a new record.
A record that was never a decision is deleted. Git history is the archive. A deleted number leaves a gap in the sequence, and the directory tolerates that. Code that pointed at the deleted record is repointed in the same change, and the pull request descriptions the deleted text summarized are linked from the deletion's pull request, so the story-level history stays reachable.
Review Checklist
A record is ready to merge when every line below holds.
- The title states the decision as a sentence.
- Date is the decision date, and Scope names what the record governs.
- The record holds one decision, and the Decision section survives the strip-the-names test.
- The Why section names at least one alternative and the trade-off accepted.
- The Consequences section names a cost, and names what enforces the decision when something does.
- Every claim that can be checked against the main branch has been checked.
- The record carries no implementation plan, open questions, build log, future enhancements, deployment procedure, or per-story amendment.
- The README index row matches the file.
Template
markdown
# ADR-NNNN: [The decision, stated as a sentence]
**Scope:** [service, group, or application]
**Status:** [Proposed | Accepted | Rejected | Deprecated | Superseded by ADR-XXXX]
**Date:** [YYYY-MM-DD, the day the decision was made]
## Context
The situation that forced the decision: what was true, what constraint applied,
what problem surfaced. Link the issue or epic.
## Decision
What we do, written as rules that hold after a rename. One decision.
## Why
The reasoning. Each alternative considered and why it lost. The trade-off accepted.
## Consequences
What becomes easier. What becomes harder. What enforces the decision, if anything does.References
- Documenting Architecture Decisions by Michael Nygard, the original proposal
- Markdown Architectural Decision Records (MADR), a widely used template
- How to create Architectural Decision Records (ADRs), and how not to by Olaf Zimmermann, a catalog of ways a record's argument goes wrong