# What YAML gives technical docs that XML and Markdown can’t

<blockquote class="border-l-4 border-indigo-500 pl-4 py-2 my-6 bg-indigo-50/50 dark:bg-indigo-950/20 text-indigo-950 dark:text-indigo-200">
**Core thesis:** For small-to-medium structured reference data that needs to live in Git and render across multiple formats, YAML functions as an ideal single source of truth—decoupling pure data from presentation without the runtime overhead of a database or the verbosity of XML.
</blockquote>

<div class="my-6">

</div>

YAML is a lightweight, human-readable data format that simplifies configuration files and data exchange. Its clarity, flexibility, and efficiency make it increasingly popular among developers, often outperforming XML, Markdown, and some database solutions in modern documentation pipelines.

The core architectural argument rests on three points:

* **The problem:** When structured reference data is mixed with presentation markup, keeping catalogs, tables, and specifications updated becomes an error-prone maintenance bottleneck.
* **The proposal:** Store reference facts in plain YAML files under full Git version control, completely decoupled from layout.
* **The payoff:** Render that single source into tables, lists, headless APIs, and UI widgets while preserving atomic Git diffs and branch-based reviews.

[Storing content in plain files rather than a database](https://redaction-technique.org/manage-content-in-files-not-databases) keeps your source code accessible to standard text-processing tools, makes peer reviews transparent, and fits directly into CI/CD build steps.

---------- | ----- | -------------------- | ----- | --------------- |
| Primary oil   | A1X   | One-cylinder engines | 15    | 0W-20           |
| Secondary oil | B2Z   | Two-cylinder engines | 17    | 5W-30           |

Looks neat, right? But as your product line grows, so does the complexity. Adding new oils, updating prices, or including extra metadata like cylinder count or warranty quickly turns into a maintenance problem.

<div class="my-6">

</div>

<figure class="my-6">
  ![Bottles of Pennzoil motor oil on a store shelf.](https://redaction-technique.org/images/blog/scalable-maintainable-technical-docs-with-yaml-large.webp)
  <figcaption class="text-sm text-gray-500 dark:text-slate-400 mt-2 text-center">Motor oil catalog: managing products, viscosities, and prices across multiple documentation formats.</figcaption>
</figure>

---

### The XML temptation

Some teams turn to **DITA reference XML**, thinking formal structure will solve the problem:

### DITA-style reference XML

```xml
<reference id="oil-types">
  <title>Oil types</title>
  <shortdesc>You will find below the recommended oil types.</shortdesc>
  <refbody>
    <section>
      <title>Primary oil</title>
      <ul>
        <li>Brand: A1X</li>
        <li>Use: One-cylinder engines</li>
        <li>Price: 15</li>
        <li>Viscosity grade: 0W-20</li>
      </ul>
    </section>
    <section>
      <title>Secondary oil</title>
      <ul>
        <li>Brand: B2Z</li>
        <li>Use: Two-cylinder engines</li>
        <li>Price: 17</li>
        <li>Viscosity grade: 5W-30</li>
      </ul>
    </section>
  </refbody>
</reference>
```

**What XML provides:** Explicit document structure, semantic tagging, and formal DTD/XSD validation.

**What becomes painful in this specific use case:** The moment prices change, new products are added, or you want to track extra attributes, the XML becomes cumbersome:

| Issue | Description |
| ----- | ----------- |
| **Hardcoded Values** | Every data point is embedded in XML. Updates require manual changes across every topic, which is error-prone. |
| **Mixing Data and Presentation** | `<ul>` and `<li>` combine field names and values, making automated sorting or aggregation difficult. |
| **Poor Scalability** | Adding oils or metadata requires repeating verbose boilerplate XML structures. |
| **Lack of Unique Identifiers** | Sections are distinguished by titles only, risking breakage in workflows if names change. |
| **Limited Reusability** | Copying sections across documents increases the risk of drift and inconsistencies. |
| **Ambiguous Values** | `<li>Price: 15</li>` lacks units or currency formatting. |
| **No Validation for Consistent Structure** | Missing fields reduce data quality over time without custom Schematron rules. |

Hardcoded XML works for tiny static lists, but it quickly becomes brittle as catalog content grows.

---

### Markdown tables: simple but limiting

Markdown tables are immediately readable in raw text:

```markdown
| Oil Type      | Brand | Use                  | Price | Viscosity Grade |
| ------------- | ----- | -------------------- | ----- | --------------- |
| Primary oil   | A1X   | One-cylinder engines | 15    | 0W-20           |
| Secondary oil | B2Z   | Two-cylinder engines | 17    | 5W-30           |
```

**What Markdown provides:** Fast authoring, high human readability in raw text, and universal static-site generator support.

**What breaks down when data scales:** Behind the tidy appearance, embedded tables carry hidden maintainability problems:

| Issue | Description |
| ----- | ----------- |
| **Hardcoded Data** | Manual updates are required for every price or product change. |
| **Lack of Semantic Structure** | Field names and values are visual table cells, not machine-readable key-value pairs. |
| **Poor Scalability** | Adding new oils or metadata requires manually refactoring every pipe and separator. |
| **No Unique Identifiers** | Rows are identified only by “Oil Type,” making programmatic referencing unreliable. |
| **Ambiguities** | Values like `Price: 15` lack units or types, causing downstream ambiguity. |
| **Limited Reusability** | Tables cannot be reused across multiple documents without manual copy-pasting. |

### The limits of Markdown tables

Markdown was designed so that the *source* should be almost as human-readable as the *output*, whether rendered as HTML, PDF, or another format. But tables are an exception: they introduce several authoring challenges.

Long lines quickly become difficult to read and edit as text editors wrap them, making it hard to distinguish one row from another. The visual benefit of tables for readers (having columns neatly aligned on vertical pipes) turns into an authoring liability:

```markdown
| Oil Type | Brand | Use  | Price | Viscosity Grade |
| - | - | - | - | - |
| Primary oil | A1X | One-cylinder engines | 15 | 0W-20 |
| Secondary oil | B2Z | Two-cylinder engines | 17 | 5W-30 |
```

**Markdown source table**

Some text editors automatically realign table columns when you edit a cell, but this triggers a full table refactor. The result is a noisy Git diff where Git flags entire lines as changed even though only a few whitespace characters moved. Conversely, if you avoid reformatting and keep column widths ragged, the raw table becomes frustrating for humans to parse.

Markdown is excellent for prose, but as your structured reference data grows, you need an architecture that decouples content from layout.

---

### Databases: powerful, but a poor fit for source content

Databases are excellent at what they're built for: structured storage, rich queries, integrity constraints, and concurrent access at scale. The question isn't whether they're capable: it's whether they fit *documentation source code*.

<div class="grid grid-cols-1 md:grid-cols-2 gap-4 my-6">
  <ConceptCard title="Where Databases Excel" subtitle="Query engine & live transactions">
    Arbitrary SQL queries, relational joins, ACID transactions, concurrent multi-user writes, and scaling to millions of dynamic records that update independently of site builds.
  </ConceptCard>
  <ConceptCard title="Where Docs-as-Code Diverges" subtitle="Source-content workflow & Git review">
    Branching, peer review in pull requests, offline text editing, commit history, zero runtime infrastructure dependencies, and static deployment alongside application code.
  </ConceptCard>
</div>

For content you want to version, review, and build into a static site, a database pulls in the opposite direction. It introduces a live service to run, migrate, back up, and secure. Most importantly, the content lives outside Git: you lose readable diffs, branch-and-PR review, and editing offline in your text editor. The data is no longer plain text you can grep, refactor with sed, or roll back with a commit.

That is the real trade-off: not query performance, but **where your content lives and how you change it**.

---

### YAML: readable, structured, and scalable

This is where **YAML** shines. It is human-readable, hierarchical, and structured, making it ideal for reference documentation that needs to scale under version control.

<div class="my-6">

</div>

Here is the canonical data source (`oil-types.yaml`):

```yaml
id: oil-types
title: Oil types
shortdesc: Recommended oil types
properties:
  headers:
    type: Type
    value: Brand
    usage: Use
  rows:
    - type: Primary oil
      value: A1X
      usage: One-cylinder engines
    - type: Secondary oil
      value: B2Z
      usage: Two-cylinder engines
```

Rendered in Markdown or HTML via a simple build script, it produces a clean presentation table:

| Oil type      | Oil brand | Use                  |
| ------------- | --------- | -------------------- |
| Primary oil   | A1X       | One-cylinder engines |
| Secondary oil | B2Z       | Two-cylinder engines |

### Benefits of YAML as a source of truth

| Feature | Details |
| ------- | ------- |
| **Separation of Data and Presentation** | Pure data lives in YAML; styling and markup live in reusable templates or components. |
| **Structured and Predictable** | Consistent schemas reduce human error and simplify automated processing. |
| **Easy to Extend** | Add new oil records or metadata attributes without altering existing layouts. |
| **Supports Automation** | Static site generators, build scripts, and CI runners consume YAML natively. |
| **Unique Identifiers** | Top-level `id` keys enable unambiguous cross-referencing across topics and datasets. |
| **Readable and Maintainable** | Self-documenting key-value pairs are easier to inspect than embedded XML tags or pipe tables. |
| **Scalable for Datasets** | Works cleanly for 5 or 500 rows while keeping diffs concise and validation automated. |

---

## YAML versatility: one source, multiple output formats

The central payoff of structured data is **versatility**. The same dataset can be rendered in multiple representations—lists, summary cards, data tables, or headless APIs—without changing a single character in the source file.

Below are three live representations generated from the exact same `oil-types.yaml` dataset:

### Output 1: Compact list for quick scanning

For user guides or mobile-friendly overviews where a wide table is unnecessary, the data renders as a clean bulleted hierarchy:

### Example: Display your data as a simple list

### Output 2: Responsive two-column summary

For summary reference pages, a two-column layout pairs each brand with its technical properties. On desktop, this renders as a table; on mobile viewports, it collapses into individual labeled rows inside cards:

### Example: Display your data as a styled two-column table

### Output 3: Dynamic, sortable four-column table

For comprehensive engineering specifications, the same YAML file feeds an interactive table featuring typed sorting (numeric pricing, integer cylinder counts, alphabetical text) and formatted currency labels:

### Example: Display your data as a dynamic HTML table

Notice the key architectural takeaway: **all three outputs derive from a single file**. If the price of `Primary oil` changes to `$16.50`, you update that single value in `oil-types.yaml`. The list, the two-column summary, the four-column table, and the API payload all update in lockstep during the next build.

---

## Easier diffs and cleaner version control

One of the most practical benefits of this architecture is version control hygiene. When data and presentation are mixed, even trivial changes produce noisy Git diffs that make code reviews slow and error-prone.

<div class="my-6">

</div>

### Evidence 1: Reordering columns in an embedded Markdown table

When you remove or reorder a column in a Markdown table, Git compares the file line by line. Every single row appears modified because the column delimiters moved:

### Git diff: reordering columns in a Markdown table

```diff
diff --git a/src/content/blog/scalable-maintainable-technical-docs-with-yaml.mdx b/src/content/blog/scalable-maintainable-technical-docs-with-yaml.mdx
index 61dadd8..0f70057 100644
--- a/src/content/blog/scalable-maintainable-technical-docs-with-yaml.mdx
+++ b/src/content/blog/scalable-maintainable-technical-docs-with-yaml.mdx
@@ -26,10 +26,10 @@ Imagine you’re an engine oil manufacturer. Every day, customers ask you which

   At first, it might seem simple. You could create a quick reference table:

-| Oil Type      | Brand | Use                  | Price | Viscosity Grade |
-| ------------- | ----- | -------------------- | ----- | --------------- |
-| Primary oil   | A1X   | One-cylinder engines | 15    | 0W-20           |
-| Secondary oil | B2Z   | Two-cylinder engines | 17    | 5W-30           |
+| Oil Type      | Use                  | Price | Viscosity Grade |
+|---------------|----------------------|-------|-----------------|
+| Primary oil   | One-cylinder engines | 15    | 0W-20           |
+| Secondary oil | Two-cylinder engines | 17    | 5W-30           |
```

*Notice:* Even though the actual product data was untouched, four lines were flagged as deleted and replaced. Reviewers cannot quickly tell if a price or viscosity was altered.

### Tip: Mitigate table diff issues with third-party tools

To partially improve the readability of table diffs, use tools like **GitHub Desktop** or `git diff --word-diff`, or configure a custom diff driver in `.gitattributes`.

<figure class="my-3">
  ![GitHub Desktop screenshot showing word-level diff highlights.](https://redaction-technique.org/images/blog/githubdesktop.webp)
  <figcaption class="text-xs text-gray-500 dark:text-slate-400 mt-1 text-center">GitHub Desktop highlighting word deletions in green, though Git still records entire line modifications under the hood.</figcaption>
</figure>

These tools help human reviewers spot changes, but Git internally still treats the entire line as modified.

### Evidence 2: Removing keys from the YAML source

By contrast, when tables are generated from YAML, removing an unused field is an atomic, legible change:

### Git diff: removing keys from YAML source

```diff
diff --git a/src/data/oil-types.yaml b/src/data/oil-types.yaml
index 11eef57..87c04df 100644
--- a/src/data/oil-types.yaml
+++ b/src/data/oil-types.yaml
@@ -5,24 +5,20 @@ shortdesc: You will find below the recommended oil types.
 properties:
   headers:
     type: Type
-    name: Brand
     usage: Use
   row_schema:
     type: str
-    name: str
     price: float
     cylinders: int
     viscosity_grade: str
   rows:
     - type: Primary oil
-      name: A1X
       price: 15.0
       cylinders: 1
       viscosity_grade: 0W-20
     - type: Secondary oil
-      name: B2Z
       price: 17.0
       cylinders: 2
       viscosity_grade: 5W-30
```

*Notice:* Only the exact lines containing `name: Brand` and the corresponding product values are removed. Every other property line remains untouched, making peer reviews instantaneous.

### Evidence 3: Updating table presentation in the component script

An even cleaner docs-as-code pattern is adjusting the **rendering component** rather than the data. If you want to hide a column across all documentation pages, you update the Astro component:

### Git diff: adjusting the table rendering script

```diff
diff --git a/src/components/table.astro b/src/components/table.astro
index c05af99..d594203 100644
--- a/src/components/table.astro
+++ b/src/components/table.astro
@@ -3,7 +3,6 @@ import data from "../data/oil-types.yaml";
 type OilRow = {
   type: string;
-  name: string;
   usage: string;
   viscosity_grade: string;
   price: number;
@@ -34,7 +33,6 @@ const wordsToNumber: Record<string, number> = Object.fromEntries(
     <thead>
       <tr>
         <th data-key="type" data-type="text">Type <span class="arrow">▲▼</span></th>
-        <th data-key="name" data-type="text">Brand <span class="arrow">▲▼</span></th>
         <th data-key="cylinders" data-type="cylinders">Cylinders <span class="arrow">▲▼</span></th>
         <th data-key="viscosity_grade" data-type="text">Viscosity grade <span class="arrow">▲▼</span></th>
         <th data-key="price" data-type="number">Price <span class="arrow">▲▼</span></th>
@@ -44,7 +42,6 @@ const wordsToNumber: Record<string, number> = Object.fromEntries(
       {rows.map((row) => (
         <tr>
           <td data-label="Type">{row.type}</td>
-          <td data-label="Brand">{row.name}</td>
           <td data-label="Cylinders">{numberToWords(row.cylinders)}-cylinder engines</td>
           <td data-label="Viscosity grade">{row.viscosity_grade}</td>
           <td data-label="Price">${row.price.toFixed(2)}</td>
```

*Notice:* A single 4-line change in the rendering component updates every table across your entire documentation site without touching a single record in `oil-types.yaml`.

---

### Growing with your YAML: The maturity model

What begins as a simple reference file can mature into a complete documentation asset pipeline:

<div class="my-6">

</div>

Our **oil-types.yaml** file scales through four stages:

1. **Structured source:** The YAML file stores pure data facts once.
2. **Multi-channel distribution:** The file generates DITA reference topics, OpenAPI JSON endpoints, and dynamic web UI components.
3. **Strong typing:** Enforcing numeric types for prices (`float`) and cylinder counts (`int`) prevents formatting errors.
4. **Schema validation:** A central JSON Schema or Zod validator in your CI pipeline guarantees that missing fields or malformed records fail the build before reaching production.

---

## Why a structured source is better than embedded tables

Tables are an effective way to present structured data in a familiar, scannable layout. However, authoring user-facing tables directly in Markdown source files ties data to presentation.

A stronger alternative is to **extract data at build time from a structured source**, such as a YAML single source of truth. This approach allows you to **render the same information in multiple ways**, each tailored to the target medium and the specific needs of your audience: whether that’s a Markdown table in documentation, a JSON payload for an API, or a dynamic HTML component in a UI. See [how Astro exposes YAML data through a live API](https://redaction-technique.org/experimental-astro-api-docs) for a working example of this distribution pattern.

By decoupling data from presentation, you gain maintainability, consistency, and flexibility as your content grows.

---

## Where YAML wins, and where it doesn’t

The title of this post is deliberately bold, so it is essential to scope the architectural claim honestly. YAML outperforms the alternatives for one specific job: small-to-medium structured reference data that builds into a static site or feeds an API under Git.

Stretch it past that job and the comparison flips, because the alternatives are not strawmen; each is strong where YAML is weak:

<div class="grid grid-cols-1 md:grid-cols-2 gap-4 my-6">
  <ConceptCard title="YAML Single Source" subtitle="Best fit: Reference data under Git">
    Small-to-medium structured reference catalogs, product specs, configuration files, and multi-format generated outputs that require clean Git diffs and branch reviews.
  </ConceptCard>
  <ConceptCard title="Markdown" subtitle="Best fit: Narrative & prose">
    Conceptual explanations, tutorials, guides, and thought leadership where content is paragraph-driven and adding a formal schema would introduce needless friction.
  </ConceptCard>
  <ConceptCard title="XML / DITA" subtitle="Best fit: Enterprise structural enforcement">
    Large-scale documentation teams requiring strict structural enforcement, controlled vocabularies, content specialization, and formal schema validation across multi-division publications.
  </ConceptCard>
  <ConceptCard title="Relational Database" subtitle="Best fit: High-scale querying & transactions">
    Datasets exceeding tens of thousands of records, arbitrary SQL queries, relational joins across tables, concurrent writes, and real-time updates that occur outside static site builds.
  </ConceptCard>
</div>

YAML also has real trade-offs and sharp edges that a fair architectural evaluation must acknowledge:

* **Indentation sensitivity:** A missing space can silently restructure an entire object hierarchy.
* **Boolean parsing traps:** In older YAML 1.1 parsers, strings like `yes`, `no`, `on`, and `off` can be coerced into booleans unless strictly quoted.
* **No built-in schema:** Unlike XML (which has native XSD/DTD validation), YAML requires an external schema validator (such as JSON Schema or Zod) to guarantee data integrity.
* **Scale limits:** Parsing a 50,000-row YAML file during build time is inefficient; flat files lack relational indexes and `JOIN` capabilities.

<blockquote class="border-l-4 border-blue-500 pl-4 py-2 my-6 bg-blue-50/50 dark:bg-slate-800/60 text-slate-900 dark:text-slate-100">
**The architectural verdict:** For small-to-medium structured reference data you want under Git and rendered many ways, YAML is the best-fit source of truth. It is not a universal replacement for prose or databases—it is a purpose-built lane for sustainable, docs-as-code reference information.
</blockquote>

---

<blockquote>
Learn more about [getting the benefits of DITA XML without its complexity](https://redaction-technique.org/strong-information-typing-without-xml-overhead). Modern docs-as-code workflows let technical writers structure information using lightweight, open tools. No XML headaches required.
</blockquote>  

## Related reading

- [Automatically insert data into a reStructuredText file](https://docs.redaction-technique.org/en/tutorials/auto-insert-data-restructuredtext/) - generating docs from a structured data source.
- [Automatically insert SQL data into a reStructuredText file](https://docs.redaction-technique.org/en/tutorials/auto-insert-sql-data-restructuredtext/) - the database-backed variant of the same idea.

## External sources

- [YAML language reference](https://yaml.org/)
- [Schema-based validation / strong typing](https://json-schema.org/)
- [OpenAPI: YAML as an API source of truth](https://www.openapis.org/)

<small>*Hero image: ["Honeycomb"](https://www.flickr.com/photos/krayker/2268587409) by [Karunakar Rayker](https://www.flickr.com/photos/krayker/), licensed under [CC BY 2.0](https://creativecommons.org/licenses/by/2.0/).*</small>

---

Source: https://redaction-technique.org/scalable-maintainable-technical-docs-with-yaml
