tsvsheet logo tsvsheet

Markdown

A computed sheet freezes into a GitHub-flavored Markdown table — one command, and results that render in any README, wiki, or docs site with no engine attached.

Everything here runs against invoice.tsvt from tsvsheet.examples:

curl -fsSO https://raw.githubusercontent.com/tsvsheet/tsvsheet.examples/main/invoice.tsvt

Freeze a sheet into a table

One engine, one grid, many renderings — --format md (alias markdown) serializes the computed grid as a GitHub-flavored pipe table. The =formula cells are gone; only their values remain:

$ tsv render invoice.tsvt --format md
| Item | Qty | Price | Amount |
| --- | --- | --- | --- |
| Widget | 3 | 4.50 | 13.5 |
| Gadget | 2 | 12.00 | 24 |
| Gizmo | 5 | 2.25 | 11.25 |
| Sprocket | 10 | 0.99 | 9.9 |
| Total |  |  | 58.65 |

Paste that into a README, a wiki page, or an issue and it renders anywhere Markdown does — no engine, no styling, no JavaScript. Each cell is escaped for the table: a literal | is backslashed so it never opens a new column, and an in-cell newline (a CHAR(10)) becomes a <br> so it never splits the row.

The sheet is optional — omit it and render reads stdin, so a table drops straight into a pipeline:

cat invoice.tsvt | tsv render --format md

Bake sheets inside a document

For a docs site, the sheet lives in the Markdown as a fenced sheet block, and the @tsvsheet/tsvsheet-remark remark host rewrites each block into a computed table at build time. Point it at any document — here one built straight from the example:

{ printf '# Order 1042\n\n```sheet\n'; cat invoice.tsvt; printf '\n```\n'; } > order.md

With output: "markdown", the plugin swaps every sheet block for its computed pipe table, and remark-stringify emits portable Markdown — the same bytes the CLI produces:

import { readFile } from "node:fs/promises";
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkStringify from "remark-stringify";
import { remarkTsvsheet } from "@tsvsheet/tsvsheet-remark";

const doc = await readFile(process.argv[2], "utf8");
const out = await unified()
  .use(remarkParse)
  .use(remarkTsvsheet, { output: "markdown" })
  .use(remarkStringify)
  .process(doc);
process.stdout.write(String(out));
$ node bake.mjs order.md
# Order 1042

| Item | Qty | Price | Amount |
| --- | --- | --- | --- |
| Widget | 3 | 4.50 | 13.5 |
| Gadget | 2 | 12.00 | 24 |
| Gizmo | 5 | 2.25 | 11.25 |
| Sprocket | 10 | 0.99 | 9.9 |
| Total |  |  | 58.65 |

Commit order.md as the source of truth and bake it on publish; the tracked file stays diffable text, and every reader gets a computed table. Drop the output option and the same block renders as a live HTML <table> instead — the remark and goldmark hosts share one contract, so a .tsvt block renders identically through the CLI, a Go pipeline, or a JavaScript one.