dbt does not care where your doc block .md files live — any .md file in your resource paths gets parsed. This flexibility means you need a deliberate organization pattern, or doc blocks end up scattered in ways that make them hard to find and maintain.
Four patterns exist in practice. Each makes different tradeoffs around discoverability, maintenance, and scalability.
Pattern 1: Per-Directory Files (dbt Labs Recommendation)
Place one .md file per directory, alongside the YAML and SQL files, with an underscore prefix so it sorts to the top:
models/marts/marketing/├── _marketing__docs.md # Doc blocks for this directory├── _marketing__models.yml # Model configs, tests, descriptions├── mrt__marketing__session.sql└── mrt__marketing__campaign_performance.sqlThe underscore prefix sorts documentation files above SQL files in directory listings. Double underscores separate the directory name from the file type, matching the naming conventions used for YAML files.
Strengths: Consistent with dbt Labs conventions. Easy to find — if you are looking at a model’s SQL, the doc blocks are in the same directory. Scales predictably as you add directories.
Weaknesses: Column descriptions that apply across directories (like customer_id) do not have a natural home. You end up picking an arbitrary directory or duplicating the doc block reference.
Pattern 2: Per-Model Files
Give each model its own .md file:
models/marts/marketing/├── mrt__marketing__customer_ltv.md├── mrt__marketing__customer_ltv.sql├── mrt__marketing__session.md├── mrt__marketing__session.sql└── _marketing__models.ymlStrengths: Maximum discoverability — every model’s documentation is right next to its SQL. Good for complex models with long, detailed doc blocks. No ambiguity about where to put a model-level doc block.
Weaknesses: Creates a lot of files. Shared column descriptions still need a home. For simple models with short descriptions, the overhead of a separate file is hard to justify when the description could live inline in YAML.
Pattern 3: Centralized Docs Folder
Put all doc blocks in a dedicated docs/ directory configured via docs-paths:
docs-paths: ["docs"]docs/├── shared_columns.md # customer_id, created_at, etc.├── marts_marketing.md # All marketing mart doc blocks├── marts_finance.md # All finance mart doc blocks└── sources.md # Source-level documentationStrengths: Clean separation of documentation from code. Shared columns have an obvious home. Good for large projects where common column descriptions should not be tied to any one directory.
Weaknesses: Documentation is physically distant from the models it describes. Developers have to navigate to a different directory to update docs. Easier to forget to update documentation when it is not visible alongside the code being changed.
Pattern 4: Hybrid (Recommended for Growing Projects)
Define doc blocks where columns are first introduced — typically in base models — and reference them from downstream intermediate and mart models. New derived fields get their own doc blocks in their respective directories.
models/├── base/│ ├── stripe/│ │ ├── _stripe__docs.md # Doc blocks for source columns│ │ ├── _stripe__models.yml│ │ └── base__stripe__payment.sql│ └── ga4/│ ├── _ga4__docs.md # Doc blocks for GA4 columns│ ├── _ga4__models.yml│ └── base__ga4__event.sql├── intermediate/│ └── ... # References base doc blocks└── marts/ └── marketing/ ├── _marketing__docs.md # Doc blocks for derived columns only ├── _marketing__models.yml └── mrt__marketing__customer_ltv.sqlThe logic: columns that come from source systems get documented at the base layer, right where they enter your project. Calculated columns and business logic fields get documented where they are created. Everything downstream inherits through {{ doc() }} references.
Strengths: Follows data lineage — documentation lives where the column originates. Minimizes duplication. Scales well because each layer only documents what it adds.
Weaknesses: Requires discipline about which layer “owns” a column’s documentation. New team members need to understand the convention to know where to look for or add doc blocks.
Choosing a Pattern
| Factor | Per-Directory | Per-Model | Centralized | Hybrid |
|---|---|---|---|---|
| Discoverability | Good | Best | Poor | Good |
| Shared columns | Awkward | Awkward | Natural | Natural |
| File count | Low | High | Low | Medium |
| Scales to 100+ models | Yes | Verbose | Yes | Yes |
| Matches dbt Labs conventions | Yes | No | Partially | Partially |
For most teams, the hybrid approach works best as projects grow. If you are starting a new project or have fewer than 30 models, the per-directory pattern is simpler and sufficient. The centralized pattern works if your team treats documentation as a distinct concern from model development — rare in practice.
Whatever you choose, decide early. Migrating doc block files between patterns is not technically difficult but is tedious and creates noisy PRs. Getting dbt-osmosis set up early helps because it enforces YAML consistency regardless of where your doc blocks live.