YAML to Markdown Table Converter

Put the config in the pull request as something people will actually look at. Entries become rows, nested blocks become dotted columns, and the differences jump out.

To convert YAML to a Markdown table, paste your file above. Each entry in the sequence becomes a table row, keys become the header, nested mappings flatten into dotted columns, and anchors and merge keys are resolved first so every row is complete. Pipes are escaped and numeric columns are right-aligned.

Want to trim it to the interesting keys? Open the app

A config diff nobody can read is a config diff nobody reviews

Changing four values across three services in a YAML file produces a diff of a dozen scattered lines, each of which needs its surrounding indentation read to know what it belongs to. Reviewers approve those, and that is how a replica count of 12 becomes 2 in production.

The same change as a table in the pull request description is four cells in a grid, next to the values they replaced. It takes ten seconds to check and it costs the author one paste.

The other use is documentation. A runbook that lists the services and their settings goes stale immediately if it is maintained by hand, and takes a paste to regenerate from the config that is actually deployed.

Worked example

A deployment config with an anchor, which is where this earns its keep:

defaults: &defaults
  log_level: info
  autoscale: true

- <<: *defaults
  service: billing-api
  replicas: 6
- <<: *defaults
  service: web-frontend
  replicas: 4
  log_level: warn

And the Markdown:

| log_level | autoscale | service | replicas |
| --- | --- | --- | ---: |
| info | true | billing-api | 6 |
| warn | true | web-frontend | 4 |

The merge key was resolved, so both rows carry autoscale and both carry a log_level even though only one of them stated it. That is the point: reading the file, you have to hold the defaults block in your head to know what the second service actually gets. In the table it is right there.

The YAML shapes it reads

  • A sequence of mappings is the ordinary case: one entry, one row.
  • Several documents separated by --- become one row each when they are all mappings, which is the Kubernetes manifest shape and the useful reading of it.
  • A single mapping becomes a one-row table.
  • Nested mappings flatten into dotted columns, so resources.limits.cpu is one column.
  • A sequence of scalars becomes a single column called value, rather than an error.
  • Anchors, aliases and merge keys are resolved by the parser before the table is built, so a manifest that reuses a block through <<: *defaults comes out with the merged values in place.

Escaping and width

A pipe inside a value ends the cell, so every one is escaped as \|, with backslashes escaped first. Config files carry pipes more often than you would think, in command strings and in shell snippets embedded as scalars.

A YAML block scalar written with | or > produces a multi-line value, and those become <br> in the cell. A startup command or a script embedded in a config is usually one of these, and it will make the table wide.

Width is the real constraint with a config as the source. A Kubernetes manifest flattened produces thirty or more columns and a table nobody can read. Cutting it down to the five keys the review is about is nearly always the right move, and the editor link above the fold is the fastest way to do it.

What the values look like by the time they get here

The YAML parser resolves types before this tool sees anything. A bare 2026-01-08 was already a date, a bare no was already the boolean false, and a bare 01730 was already the number 1730.

Values written that way in the source appear in the table as what the parser made of them, because that is genuinely what the file says and showing something else would be a fiction. A file whose author quoted carefully comes through exactly as written.

Frequently Asked Questions

Are anchors and merge keys resolved?

Yes, and it is the main reason to use this for a review. A file that merges a defaults block into six entries produces six complete rows, so a reviewer can see what each service actually gets without holding the defaults in their head.

How is nesting handled?

Nested mappings flatten into dotted columns, so resources.limits.cpu is one column. Past four levels the value is kept as text in a cell, because a table header of six-part names is unreadable.

What happens to a block scalar?

A multi-line value becomes a cell containing br tags, so it renders as multiple lines. An embedded startup command or script will make the table wide, which is usually a sign that column should be left out of the review table.

Are pipes escaped?

Every one, with backslashes escaped first so a value ending in a backslash cannot eat the escape that follows. Config files carry pipes in command strings more often than people expect.

Why does a value look different from what the file says?

Because YAML's own parser resolved it before this saw it. A bare 2026-01-08 is a date, a bare no is false, and a bare 01730 is 1730. That is what the file genuinely means, and showing the raw text instead would misrepresent it.

My manifest produces thirty columns. What should I do?

Cut it down. A flattened Kubernetes manifest has more keys than any table can carry, and a review table is only useful if it fits on a screen. The editor link above the fold opens the same data where you can keep the five keys the change is about.

Put the config where reviewers will read it

Anchors resolved, nesting flattened, pipes escaped. One paste into the pull request.

Back to the converter