dbt generates a doc site from YAML and the DAG. Free, accurate, and most teams skip it because nobody writes descriptions.
Writing descriptions
models:
- name: fct_orders
description: |
One row per order. Order-level measures (total, tax, shipping).
For line-item analysis, see fct_order_items.
Grain: one row per order_id.
Update frequency: every 30 minutes (incremental).
columns:
- name: order_id
description: "Unique per order. Surrogate to source's id."
tests:
- unique
- not_null
- name: customer_id
description: "FK to dim_customers. Stable across SCD versions."
- name: order_total
description: |
Total paid by customer in transaction currency.
See revenue_usd for USD-converted measure.
- name: order_placed_at
description: "UTC timestamp when checkout completed (NOT cart-create)."
Minimum: one paragraph per model (grain, freshness, use). One sentence per column.
Building and serving
dbt docs generate
dbt docs serve # localhost:8080
Production: host on GitHub Pages, dbt Cloud, or S3 static site. Refresh on every merge.
Doc blocks for reuse
-- macros/docs/customer_segment.md
{% docs customer_segment %}
Customer segment computed by marketing:
- `enterprise` — ACV >= $50K
- `smb` — ACV $5K-$50K
- `consumer` — individuals
- `unknown` — default for new
{% enddocs %}
Reference:
columns:
- name: segment
description: "{{ doc('customer_segment') }}"
Single source of truth. Changes propagate.
What to write
The minimum:
- Models: grain, update frequency, "use this for X".
- Columns: meaning, units/currency, NULL semantics, caveats.
Plus:
- Links to related models.
- Edge cases (
refunds appear as negative revenue). - Source-of-truth notes.
What NOT to write:
- The SQL — readable already.
- Implementation details.
- Pure restatements.
YAML location
One schema.yml per folder is conventional:
models/marts/core/
├── fct_orders.sql
├── dim_customers.sql
└── schema.yml
Sources and macros
sources:
- name: shopify
description: "Shopify CDC via Fivetran. 30-min sync."
{% macro cents_to_dollars(cents_column) %}
-- Converts cents to dollars (2 decimal places)
({{ cents_column }}::numeric / 100.0)::numeric(10,2)
{% endmacro %}
Common mistakes
- Descriptions for some models, not all.
- One-line descriptions that say nothing.
- Documenting implementation, not meaning.
- Stale docs (SQL changed, description didn't).
- Never building/serving docs.
Takeaway
Two sentences per model. One per column. Doc blocks for shared definitions. Generated and served on every merge. Tiny cost, huge team-multiplier.