Specified
A 1.0 specification, a conformance suite of 197 cases, and CI that checks the specification — itself a GEML document — on every push.
Read the spec
Plain text people read. Blocks with names that agents get, set, add and delete — and a write that would break the document is refused. Works on the Markdown you already have.
Every kind of content is one block shape: === type {attributes}, a body, ===. Headings are blocks too, and every block has a name.
=== meta
title = "Budget plan"
===
# Budget plan {#top}
Prose with *emphasis*, a [[#fy25]] reference and a footnote[^1].
=== table {#fy25 format=csv header=1}
Segment, Q1, Q2, Q3, Q4
Cloud, 8, 10, 12, 14
Platform, 5, 6, 7, 9
===
=== view {#fy25-total src=#fy25 compute="FY = Q1 + Q2 + Q3 + Q4"}
===
=== diagram {#rev format=geml-chart data=#fy25-total type=bar x=Segment y=FY}
===
=== embed {src=#fy25}
===
[^1]: Every block above has an address.| Construct | Syntax |
|---|---|
| Typed block | === type {#id key=value} … === |
| Heading with an id | ## Title {#id} — derived from the text when omitted |
| Reference | [[#id]] · across documents [[doc.geml#id]] |
| Link | [text](https://example.com) |
| Embed (a projection, not a copy) | === embed {src=doc.geml#id} |
| Inline projection | ![[doc.geml#id]] |
| Table | pipes, or === table {format=csv header=1} |
| Derived view | === view {src=#table compute="…" summary="…"} |
| One cell, one leaf | #fy25[2]["Q1"] · #intake["fields"][1]["name"] |
| Data | === data {format=json} (also jsonl, yaml, edn) |
| Chart bound to data | === diagram {format=geml-chart data=#id type=bar x=… y=…} |
| Meta value in prose | |
| Footnote | [^note] … [^note]: text |
| Hidden block · author note | {hidden} · a line starting with %% |
| Prose as a block | === text {#id} … === |
| Vocabulary | profile = "geml-media/v1" in === meta |
The cheat sheet lists every construct with an example. The playground has seven chapters, each a real .geml file you can edit.
Nothing is converted. plan.md stays plan.md; every heading is an address.
$ geml list plan.md
#data-warehouse-migration heading h1 L1-39 Data warehouse migration
#summary heading h2 L3-8 Summary
#migration-plan heading h2 L9-17 Migration plan
#capacity-and-cost heading h2 L18-27 Capacity and cost
#risks heading h2 L28-33 Risks
#open-questions heading h2 L34-39 Open questions
$ geml set plan.md '#migration-plan' --in new-plan.md
wrote plan.md
$ git diff --stat
plan.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
$ geml set plan.md '#risks' --in bad.md
error: replacement would break the document: unresolved reference `#checksum-job` (line 30); not written
exit=1
$ geml revert plan.md '#summary' --rev changed
reverted #summary to 20260929T133449Z-6b606e52get reads one section. set replaces one section and leaves every other byte alone. A write whose result would be broken is refused before it reaches disk. revert rolls one section back and keeps the edits made elsewhere in the meantime.
Headings are as fine as Markdown goes. When a section is too coarse — a table, one cell, a chart that must agree with its table — the .geml format gives every block a name and every cell a coordinate.
npm i -g @geml/geml # the geml command (Node 22+)
npx -y @geml/geml skill install # Claude Code: authoring skill + CLI + MCP server, user-global
claude mcp add --scope user geml -- npx -y @geml/geml mcp --root . # or any MCP clientRead .geml in the browser with the Chrome extension; editor and agent integrations are listed under Get Started.
.gemlhistory sidecar rolls one block back and leaves concurrent edits alone.geml check..md unchanged; .geml is a separate language with one normative specification and a conformance suite, and it projects back to Markdown or HTML with --to md|html — block ids and live charts are reported as lost, not hidden..geml.check; that is what revert is for.The parser is @geml/geml on npm. Spec 1.0 is stable: rules already in it will not shift under you; a breaking change bumps the spec version and ships with updated conformance cases.