Architecture decision records: a practical guide and template
On this page
An architecture decision record (ADR) is a short document about one important technical decision. It covers the situation that forced it, the options considered, what was chosen, and what follows from it. It records why a choice was made, which is the part code cannot tell you.
That helps people who join the team later, people who forget the reasoning, and anyone reviewing the decision when circumstances change. This page explains the sections, gives a template to copy, and shows a real example.
When to write an ADR
Not every decision needs one. Write an ADR when the decision:
- has a long-term effect on the system
- is hard or expensive to reverse
- affects several teams or components
- involves a trade-off between factors that matter
Write it while the decision is being made, before implementation starts. The value is in recording the reasoning while it is fresh, so an ADR written after the work is done loses most of it.
The metadata table
Every ADR starts with a table of quick reference fields:
- title: a clear, descriptive name for the decision.
- status:
proposed, thenaccepted,rejectedordeferred. Later it can becomesupersededif a new ADR replaces it. - impact:
low,mediumorhigh, so the important decisions are easy to find. - Driver: the person leading the decision.
- Approver: the person with authority to approve it.
- created: the date the ADR was written.
- Outcome: the final result and any implementation notes.
Keep the status current as the decision moves through approval.
The sections
- Context and problem statement. What situation led to the decision, what problem you are solving, why it needs deciding now, and any background a reader needs.
- Decision drivers. The factors that shape the choice: technical constraints, business needs, team skills, cost or deadlines.
- Considered options. Every serious alternative, with its pros and cons. Include the ones you dismissed quickly, including doing nothing.
- Decision outcome. The chosen option, why it beat the others, and any immediate actions.
- Consequences. What the decision means in practice, both good and bad. Be specific about risks.
- Links. Documents, discussions and resources that informed the decision.
Aim for one or two pages at most. Include enough for someone to understand the decision without asking the author.
Template
Copy this into a new file, give it the next sequential number, and fill in every section.
| Field | Value |
| -------- | ------------------------------------------------------ |
| title | [Short, descriptive title for the decision] |
| status | proposed |
| impact | low / medium / high |
| Driver | [Name and role of the person driving the decision] |
| Approver | [Name and role of the person with approval authority] |
| created | [YYYY-MM-DD] |
| Outcome | [Fill in once resolved: Accepted, Rejected or Deferred] |
## Context and Problem Statement
[The situation that prompted this decision. What is the problem, and why
does it need to be addressed now?]
## Decision Drivers
- [Driver one]
- [Driver two]
- [Driver three]
## Considered Options
### Option 1: [Name]
- Pros: [Advantages]
- Cons: [Disadvantages]
### Option 2: [Name]
- Pros: [Advantages]
- Cons: [Disadvantages]
### Option 3: [Name]
- Pros: [Advantages]
- Cons: [Disadvantages]
## Decision Outcome
Chosen option: [Option name]
Reason: [Why it was chosen over the alternatives]
Immediate actions:
- [Action]
## Consequences
### Positive
- [Positive consequence]
### Negative
- [Negative consequence or risk]
## Links
- [Link or reference]Where to store ADRs
Keep ADRs in the project repository, next to the code they describe, with a number at the start of each file name:
docs/
architecture/
decisions/
001-use-react-typescript.md
002-adopt-microservices.md
003-choose-postgresql.mdTreat an accepted ADR as fixed. Only correct typos. If circumstances change, write a new ADR that supersedes the old one and mark the old one superseded. That keeps a record of what was known when each decision was made.
Worked example: a queries layer
This ADR records a refactor that seems obvious later, but is worth writing down before someone asks why pages do not just call the database directly.
# Introduce a Queries Layer for All Prisma Reads
| Field | Value |
| -------- | ---------------------------------------- |
| title | Introduce src/lib/queries/ layer |
| status | accepted |
| impact | medium |
| Driver | Paul (lead developer) |
| Approver | Paul (lead developer) |
| created | 2026-04-12 |
| Outcome | Accepted, migrated across all pages |
## Context and Problem Statement
Prisma read calls are scattered directly across page components. The same
filter shapes repeat across files, complex `include` blocks are buried in
component code, and there is no single place to look when you want to
understand what data a given model exposes. Pages are harder to read because
data access and rendering are mixed together.
## Decision Drivers
- Discoverability: all read patterns for a model should be in one place
- Reuse: common queries (such as project by slug) are duplicated across pages
- Testability: query functions should be testable without rendering a page
- Clarity: pages should focus on rendering; data access belongs elsewhere
## Considered Options
### Option 1: Queries layer in src/lib/queries/
Pure read functions, one file per Prisma model. Pages import from the queries
layer. No page imports from the database client directly.
- Pros: clear ownership, easy to discover, testable in isolation
- Cons: migration cost across existing pages
### Option 2: Keep inline queries in pages
Status quo. Each page owns its own Prisma calls.
- Pros: no migration needed
- Cons: continued duplication, no discoverability, harder to test
### Option 3: Repository pattern with interfaces
Abstract the data layer behind interfaces, inject into pages.
- Pros: fully decoupled from Prisma
- Cons: significant overhead for a single-database app; premature abstraction
## Decision Outcome
Chosen option: Queries layer in src/lib/queries/
All Prisma reads are extracted into typed functions grouped by model under
`src/lib/queries/`. Pages call query functions instead of Prisma directly.
Mutations remain in `src/lib/actions/`. No page file may import the database
client directly.
## Consequences
### Positive
- Query functions are discoverable by model name
- Duplicate `where` clauses are consolidated to a single definition
- Pages are shorter and focused on rendering
- Query functions are straightforward to unit test
### Negative
- One-time migration cost across all existing pages
- Developers need to know to look in `lib/queries/` instead of defining inlineThis ADR is short because the decision is narrow. It needs a clear rule and the reasoning behind it, not a long analysis. Anyone reading the codebase later can open it and understand the intent without finding the original author.
Common mistakes
- Being vague. "Use microservices for better scalability" says little. "Split user management into a separate service to handle the user growth expected next quarter" says what and why.
- Leaving out alternatives. Record the options you considered, even the ones you dismissed quickly.
- Letting the status go stale. An ADR still marked
proposedafter the work shipped misleads the next reader. - Writing it after implementation. By then you are describing what was built, and the reasoning is already fading.