mundimark/awesome-markdown collects projects that set out to patch or replace Markdown, and GEML itself is in its "Beyond Markdown" section. This page looks at them one by one: what each does, where it collides with GEML, and where it goes another way. A few projects off the list are closer to GEML; they are included too, marked separately.
Verdict. No project does all three of GEML's things at once: one typed block, an addressable id on every block, and references checked at build time and before every write, with an error stopping the write.
Taken one at a time, each has been done earlier or deeper than in GEML. Generic blocks with attributes are common ground for Djot, Pandoc, MyST and Markdoc; validation with rule ids exists in Markdoc, mystmd and Carve; and querying and rewriting Markdown by selector is more of a language in mq than in geml get. Where GEML differs is binding the three together, and adding a command line that writes by block, MCP, and per-block history.
Closest in features is Carve, but its reach is still small. It sits in the same section of the list as GEML, was created only this May, and already has lint, a JSON AST, patch-based editing, an MCP server and an agent skill — it is moving fast; but it has only 32 stars and no outside users could be found. Weighed by reach, it is a project worth tracking, not the main rival for now. It differs from GEML in three ways: its addresses are heading ids and AST paths, writes go through confirmed patches, and it is not Markdown-compatible.
Head-on the same problem, a similar answer. Partial overlap on the same road for one thing. Worth borrowing has something GEML lacks. Different path a different goal or layer, or GEML's foundation. The first four groups come from the list; the last was added from beyond it.
| Project | What it is | Where it stands to GEML | Verdict | |
|---|---|---|---|---|
| A new generation of lightweight markup · the list's "Beyond Markdown" section | ||||
| 1 | Carve2026-05 · 32 stars | A "post-Markdown" built on Djot. It has an EBNF grammar, 2,200 test cases and three implementations (JS, Rust, PHP), plus lint, a JSON AST, patch-based editing, an MCP server and an agent skill. | The most overlap in its section: it checks broken references and lets agents select and edit. Addresses are heading ids or AST paths, not ids an author writes on any block; cross-references can only point at headings and numbered captions. | Head-on |
| 2 | Djot2022 · 2,046 stars | John MacFarlane's fix for CommonMark's hard parsing cases, adding attributes, generic containers and inline containers. Pandoc reads and writes it since 3.1.12. | On the same road with "a generic block plus attributes". No block addressing, #id references are not checked, no embed syntax, and extending it means writing AST filters. | Partial overlap |
| 3 | VersaDok2025 · 17 stars | A new language from kramdown's author: <<< >>> generic blocks, and ::name: extension blocks that can take over the raw body. The author calls it alpha. | Its extension blocks are the only one of the four close to "the type decides how the body is read". No checks, no addressing. | Partial overlap |
| 4 | Texti2017 · 9 stars | A proposal merging Markdown, wiki markup and LaTeX; headings become == Title ==. | A 2017 draft whose parser is an empty module, untouched since. | Different path |
| Long-standing syntax extensions · the list's "Markdown Syntax Extensions" and "Libraries" | ||||
| 5 | MultiMarkdown | Adds metadata, tables, footnotes, cross-references and whole-file transclusion {{file}}. Version 7 is in pre-release. | Its feature list overlaps GEML's, but every feature is a special-case syntax. | Different path |
| 6 | Markdown Extra | PHP Markdown's extensions: heading {#id}, fenced code, tables, definition lists, abbreviations. | The {#id} on GEML headings comes from this line. | Different path |
| 7 | Markdown Extended | A PHP parser meant to merge Extra and MultiMarkdown into one spec. No updates since 2024. | As above. | Different path |
| 8 | CriticMarkup | Revisions written in the text: additions, deletions, substitutions, comments, highlights. | GEML has revision history but cannot express "a suggestion not yet accepted". | Worth borrowing |
| 9 | kramdown · Pandoc | kramdown's attribute lists {: #id}; Pandoc's fenced divs, JSON AST and filters. | Forerunners of the generic block, and of "the document becomes an AST first". | Partial overlap |
| Specified dialects · the list's CommonMark, GFM and VFMD sections | ||||
| 10 | CommonMark | An unambiguous Markdown spec and test suite. | GEML's prose inherits from it: a foundation, not a rival. | Different path |
| 11 | GFM | CommonMark plus tables, task lists, strikethrough and autolinks. | When GEML reads a .md directly, it follows Markdown's rules where the two syntaxes disagree, and GitHub's heading anchors work as link targets. | Different path |
| 12 | VFMD | Another unambiguous spec. | Not updated since 2016. | Different path |
| Tools · across the list | ||||
| 13 | mq2025 · 1,040 stars | Queries and rewrites Markdown with a jq-like syntax, with an MCP server. | Does the same job as geml get/find/list. Addresses are derived from selectors and heading text, and its MCP is read-only by design. | Head-on |
| 14 | darnlink2026-07 · 19 stars | Anchors files with UUIDs and fixes links after a file moves; usable as a CI gate. | File-level reference checking and repair. GEML notices that a cross-document reference broke, but does not repair it. | Worth borrowing |
| 15 | mdcode2024 · 28 stars | Two-way sync between code blocks and source files, taking snippets by region. | The same job as code {src=}, and it can push changes back to the source file. | Worth borrowing |
| 16 | markedpp | A Markdown preprocessor: table of contents, heading numbers, !include. | A coarse-grained embed; a missing file is left as written, with no error. | Different path |
| 17 | Slidev48,891 stars | Vue components in Markdown slides. Since 2026-07 it ships an MCP that reads and writes by slide. | On the same road with an MCP that reads and writes by unit. The unit is a slide and the address its number, which shifts once a slide is inserted. | Partial overlap |
| 18 | demarkus2026-02 · 16 stars | A QUIC-based Markdown protocol and server, as versioned memory for agents. | On the same road with agent reads and writes and version history. It reads a section by heading slug, but writes only whole documents. | Partial overlap |
| 19 | MarkWay | A convention for websites to return Markdown instead of HTML to agents. | Only the premise "Markdown is the format for agents" is shared. No code. | Different path |
| 20 | mk-notes | One-way sync from Markdown to Notion. | Hardly any overlap; untouched since 2025-12. | Different path |
| Beyond the list · a few closer to GEML | ||||
| 21 | MarkdocStripe · 8,494 stars | Documents hold only tags and attributes; their meaning is defined by the host's schema and validated item by item, and errors have stable ids. | Closest to GEML in design: declarative, closed, checkable. No block addressing and no write commands. | Partial overlap |
| 22 | MyST | Jupyter Book's Markdown: a directive registry and labelled cross-references. | On the same road with generic blocks and rule ids; a broken reference only warns. | Partial overlap |
| 23 | Quarto | A Pandoc-based scientific publishing system with executable code blocks and cross-referenced figures. | Computational documents; GEML has only computed columns and summary rows in tables. | Partial overlap |
| 24 | AsciiDoc | Blocks with attributes, include::, <<xref>>: a complete document language. | On the same road with blocks and embeds; a broken reference is hidden by default. | Partial overlap |
| 25 | MDX19,809 stars | JSX components written in Markdown. | The document is a program: a different road from GEML's closed vocabulary. | Different path |
Markdown's blocks are special cases, one by one: code, quotes, lists and tables each have rules of their own. Nearly every successor converged on the same fix: one generic container, told apart by attributes. Where they differ is whether the set of types is open or closed, whether ids have rules, and how the body inside a block is parsed.
{#warn .warning} ::: warning Don't run this command on the production database. :::
::: {#warn .warning} Don't run this command on the production database. :::
```{warning} :name: warn Don't run this command on the production database. ```
{% callout type="warning" #warn %} Don't run this command on the production database. {% /callout %}
=== note {#warn} Don't run this command on the production database. === === table {#cost format=csv header=1} Item,Cost Hosting,120 === === data {#vars format=json} {"owner": "docs"} ===
| Types | id | Body | |
|---|---|---|---|
| Djot | Open; any class | Attribute #id, optional | Always parsed as Djot |
| Carve | Fixed names for admonitions; the rest become divs | Attribute #id or heading slug | Always parsed as Carve |
| VersaDok | ::name: extension blocks, defined by the processor | Attribute #id | An extension can take over the raw lines |
| Pandoc | Open; left to filters | Attribute #id, optional | Always parsed as Markdown |
| MyST | A registry; an unknown directive warns | :name: or (label)= | Each directive decides |
| Markdoc | Declared in a schema; an unknown tag is an error | Attribute #id | Parsed as Markdoc |
| GEML | Listed in the spec, extended by profiles; an unknown type warns | {#id}; a duplicate is an error | By type: code, tables and data are raw text, a note is prose |
Nearly every variant can give a block an id and link to it. Where they part is what the tool says when a link points at an id that does not exist.
Djot / Pandoc: See [Budget](#budjet) MyST: See {ref}`budjet` Quarto: See @fig-budjet AsciiDoc: See <<budjet>>
| Tool | When the target is missing |
|---|---|
| Djot | #id not checked djot.js warns only about reference-style links it cannot find |
| Pandoc | not checked Outputs a broken link as usual |
| Carve | lint carve lint reports broken-crossref and exits non-zero; cross-references can only point at headings and numbered captions |
| mystmd | warning Rule id referenceTargetResolves |
| Quarto | warning Unable to resolve crossref |
| Asciidoctor | info possible invalid reference, hidden by default |
| Markdoc | links not checked Validates only tags, attributes and variables; errors have stable ids such as attribute-undefined |
# Budget {#budget}
Hosting is 120 a month.
# Appendix {#appendix}
See [[#budjet]].
$ geml check refs.geml error: unresolved reference `#budjet` (line 7) 1 error(s) exit 1 $ geml set refs.geml '#budget' --in new.geml error: refused by an error the document ALREADY had, which this edit did not cause: unresolved reference `#budjet` …; not written — repair it first
The edit is to the #budget section and the broken reference is in #appendix, yet the write is still refused. The diagnostic unresolved-reference is in the spec's Appendix A, at severity error. A writer does not keep stacking changes on a document that is already broken.
tag-placement-invalid and the like), mystmd (referenceTargetResolves) and Carve (broken-crossref) all do, the same idea as the diagnostic catalogue in GEML's Appendix A. Carve's lint also exits non-zero and can serve as a CI gate, as geml check does.Taking or changing one part instead of writing the whole file is the core of GEML's command line. Two tools on the list do neighbouring things: mq queries and rewrites Markdown with a jq-like syntax, and darnlink anchors links with UUIDs, so they can be fixed after a file moves.
$ mq '.h(2)' README.md # every level-2 heading $ mq '.code("rust")' README.md # every rust code block $ mq 'section::section("Installation")' README.md $ mq -U '…' README.md # rewrites, printing the whole file to stdout
section("Installation") finds nothing.-U prints to stdout only; writing back to the file takes the companion mq-mount with --write, or mq-db with --write-back. Nothing is checked before writing.$ geml list doc.geml #install heading h1 L1-8 Install #install-before-upgrade prose L3-3 #upgrade heading h2 L5-8 Upgrade #usage heading h1 L9-12 Usage $ geml find Upgrade . doc.geml #upgrade $ geml get doc.geml '#install' --head # Install {#install} $ geml rename doc.geml '#install' '#setup' # Install {#setup} See [[#setup]]. # references change with it $ geml list doc.geml --within '#install' #install-before-upgrade prose L3-3 #upgrade heading h2 L5-8 Upgrade $ geml get complex-form.geml '=== form-field {type=date}' 4 `form-field` blocks (L48-49 #founded · L99-101 #iso-expiry · …)
The address is the #id the author wrote, untouched when the heading is renamed; a paragraph without an id still gets an address derived from its position, such as #install-before-upgrade. Selectors can also filter by attribute and narrow to one section with --within; the last command is run on the repository's GEP-0008 form example. Every write rechecks the whole document first.
geml get/find/list, and likewise has both a command line and an MCP. GEML's selectors filter by type, attribute and content address, such as === code {lang=rust} or {.warn}, and narrow to one section with --within; what mq has besides is pipes and map, and selecting by heading level as in .h(1..3).<!-- the target file's frontmatter --> --- uuid: 7f3a1e2c-… --- <!-- the referring side --> [Design doc](docs/design.md) <!-- uuid: 7f3a1e2c-… -->
$ darnlink check . # 0 clean · 2 integrity problems · 3 unanchored links $ darnlink . --write # after a file moves, rewrites its path by UUID
See [[design.geml#arch]].
| What happened | GEML | darnlink |
|---|---|---|
| A block's id was renamed | geml rename updates every reference | Does not deal with blocks |
| The target file moved | error Reported, but not repaired | Finds the new path by UUID and rewrites it |
| As a CI gate | geml check exits non-zero | darnlink check exits non-zero, with a pre-commit hook |
geml uses to find a moved file under the root. Nothing in GEML has a place for this today; it belongs on the to-do list.Putting content from elsewhere into a document is the most common extension people ask for. On the list, mdcode, Slidev, markedpp and MultiMarkdown each have a syntax for it, and AsciiDoc, beyond the list, has one too. They part in two places: whether they refer by line number or by name, and whether a miss is an error.
```go file=main_test.go region=example … ```
A #region example comment marks a piece of the source file. mdcode update pulls the source into the document; mdcode extract pushes changes in the document back to the source file.
<<< @/snippets/demo.ts#region-name {2,3|5} --- src: ./other.md#2,5-7 slides from another deck, by number ---
!include (test.js lang=javascript) markedpp: left as written if missing {{chapter2.md}} MultiMarkdown: the whole file include::part.adoc[tag=intro] AsciiDoc: one piece, by tag
=== code {#add lang=js src=src/math.js#L1-3} === === code {#gone lang=js src=src/math.js#L40-45} === === embed {src=guide.geml#install part=intro} ===
$ geml check code.geml error: code source `src/math.js#L40-45`: the file has 7 line(s), so lines 40-45 no longer exist — the range is stale (line 6) 1 error(s)
embed takes a section of another document by id, and part= picks the whole section, its heading, its intro or its body. The embedded document is first parsed on its own rules, and its references are checked too.
There are two ways to put a chart, a form or something interactive into Markdown. One lets the document contain component code; the other has the document write only declarations, which a renderer interprets against a vocabulary.
import { Chart } from './Chart.jsx' # Quarterly revenue <Chart data={rows} type="bar" />
--- layout: two-cols --- # Quarterly revenue <BarChart :data="rows" />
=== table {#q format=csv header=1} Quarter,Revenue Q1,120 Q2,180 === === diagram {#rev format=geml-chart data=#q type=bar x=Quarter y=Revenue} ===
The chart's data is a table in the same document, referred to as #q, and the reference is checked. There is no executable code: section 1 of the spec says "There is no raw-HTML escape hatch".
"Let an agent read and write one part instead of rewriting the whole" became common ground this year. Slidev added an MCP in July, Carve's MCP was created in September, and mq and demarkus each have one. They differ in three places: whether the address is stable, whether a write is checked first, and whether a bad change can be rolled back.
| Server | Reads by | How it writes | Address | Before a write | History |
|---|---|---|---|---|---|
| Carve carve-mcp, 14 tools | Heading, footnote, a kind of node, AST path | Makes a revertible patch carrying a source hash; never writes the file itself | Heading id or AST path | Lint on request; a patch is void once the source changes | Patch reverts |
| Slidev built in, 2026-07 | One slide | Edits, inserts, deletes or moves a slide, written back to the .md | Slide number; inserting a slide shifts every one after it | None | None |
| demarkus 15 tools | A whole document, or a section by heading slug; an outline first for large ones | Publishes or appends a whole document with an expected version; offers merge candidates on conflict | Path, content hash, heading slug | Version-conflict detection | Whole documents, a server-side hash chain |
| mq mq-mcp | A section, or a kind of node | No writes | Selector, heading text | n/a | None |
| GEML 11 tools | A block, a section, a coordinate | Writes the file directly, by block or by section | The #id the author wrote | Rechecks the whole document and refuses a broken one; with a content address in the selector, a block whose content changed is not written | Per block, .gemlhistory |
=== note@<hex> {#id} does the same check: once the content changes it no longer matches, and nothing is written. What is missing is get --json and the MCP handing that hash straight to the caller.CriticMarkup is the only project in the list's syntax-extensions section that deals with revisions.
Hosting is {~~120~>150~~} a month.
{>>revised to the new quote<<}
{++Backups included.++}
Insert {++ ++}, delete {-- --}, substitute {~~ ~> ~~}, comment {>> <<}, highlight {== ==}. The marks stay in the text and go only once the change is accepted or rejected.
$ geml history save doc.geml -m "revised to the new quote" $ geml history get doc.geml --json [ { "id": "20260930T…", "summary": "revised to the new quote", "offset": 0, "current": true }, … ] $ geml revert doc.geml '#cost' --rev 20260930T…
History lives in the .gemlhistory sidecar and rolls back by block. The text stays clean, but GEML cannot express the state "a suggestion not yet accepted".
Four things: what is really unique, which rival to face squarely, what is worth borrowing, and what is no cause for worry.
Typed blocks, stable ids the author writes, and a whole-document check before every write: each has been done on its own, but only GEML puts them together. Add a command line and an MCP that write by block, per-block history, and reading existing .md directly: Carve and Djot both need the document converted first, and GEML does not.
Same section, same story: lint, a JSON AST, patch-based editing, MCP, an agent skill. Created only this May, it already has 64 repositories, 2,200 test cases and three implementations, with dozens of commits a day at the end of September; but it has only 32 stars, and no outside users could be found. Weighed by reach, it is a project to track, not today's main rival. It differs from GEML in a stable address on any block, a check on every write, and compatibility with existing Markdown; whenever GEML is presented, those three should come before the syntax.
The two head-on projects are both small: Carve has 32 stars, mq 1,040. The big ones are Slidev (48,891), Pandoc (46,456), MDX (19,809) and Markdoc (8,494). Judge the effect on GEML by the latter: Slidev shows that an MCP reading and writing by unit is already in a mainstream tool, and Markdoc is the best frame of reference for presenting GEML.
Attributes and ids on inline elements (Djot, Carve); referring to a source snippet by name (mdcode's and Slidev's regions, AsciiDoc's tags); pushing code changes in a document back to the source file (mdcode); repairing cross-document references after a file moves (darnlink); pending suggested changes (CriticMarkup); handing the content hash to the caller for a check before writing (Carve, demarkus).
CommonMark, GFM and VFMD are where GEML's prose comes from; old extensions like MultiMarkdown and Markdown Extra are collections of special-case syntax, which GEML has folded into one kind of block. MDX is the other road, "the document is a program". MarkWay and mk-notes sit at the layer of protocols and sync, not of document formats.
get --json and the MCP return the content hash, for a check before writing with the content address in a selector; and refer to source by region, added to code {src=}.Repository data comes from the GitHub API, on 2026-09-30. "Last push" is the date of the repository's last push, not necessarily its last feature commit.
| Project | Repository | Stars | Created | Last push | License |
|---|---|---|---|---|---|
| Carve | markup-carve/carve | 32 | 2026-05-13 | 2026-09-30 | MIT |
| Djot | jgm/djot | 2,046 | 2022-07-11 | 2026-07-01 | MIT |
| VersaDok | gettalong/versadok | 17 | 2025-01-09 | 2026-07-05 | MIT |
| Texti | texti/texti.github.io | 9 | 2017-07-14 | 2018-08-04 | CC0 |
| MultiMarkdown 6 / 7 | fletcher/MultiMarkdown-7 | 678 / 56 | 2017-01-19 | 2026-09-22 | See repo |
| Markdown Extra | michelf/php-markdown | 3,462 | 2010-06-06 | 2025-06-17 | See repo |
| Markdown Extended | e-picas/markdown-extended | 38 | 2013-06-02 | 2024-03-02 | See repo |
| CriticMarkup | CriticMarkup/CriticMarkup-toolkit | 849 | 2013-01-10 | 2021-03-04 | See repo |
| kramdown | gettalong/kramdown | 1,782 | 2008-11-30 | 2026-01-31 | See repo |
| Pandoc | jgm/pandoc | 46,456 | 2010-03-20 | 2026-09-29 | GPL-2.0 |
| CommonMark | commonmark/commonmark-spec | 5,149 | 2014-08-14 | 2026-04-27 | See repo |
| GFM | github/cmark-gfm | 1,133 | 2016-12-01 | 2026-09-28 | See repo |
| VFMD | vfmd/vfmd-spec | 32 | 2013-09-01 | 2016-10-29 | None stated |
| mq | harehare/mq | 1,040 | 2025-02-24 | 2026-09-30 | MIT |
| darnlink | txemi/darnlink | 19 | 2026-07-11 | 2026-09-27 | GPL-3.0 |
| mdcode | szkiba/mdcode | 28 | 2024-01-08 | 2026-02-23 | MIT |
| markedpp | commenthol/markedpp | 39 | 2014-09-01 | 2025-08-18 | MIT |
| Slidev | slidevjs/slidev | 48,891 | 2021-04-24 | 2026-09-16 | MIT |
| demarkus | latebit-io/demarkus | 16 | 2026-02-14 | 2026-09-30 | AGPL-3.0 per README |
| MarkWay | RaysunKR/MarkWay | 2 | 2026-03-31 | 2026-06-03 | MIT |
| mk-notes | Myastr0/mk-notes | 125 | 2025-01-29 | 2025-12-23 | MIT |
| Markdoc | markdoc/markdoc | 8,494 | 2022-05-09 | 2026-09-16 | MIT |
| MyST | jupyter-book/mystmd | 525 | 2020-07-21 | 2026-09-28 | MIT |
| Quarto | quarto-dev/quarto-cli | 6,033 | 2020-09-25 | 2026-09-30 | See repo |
| AsciiDoc | asciidoctor/asciidoctor | 5,218 | 2012-06-01 | 2026-09-01 | See repo |
| MDX | mdx-js/mdx | 19,809 | 2017-12-24 | 2026-09-29 | MIT |
| GEML probe | Covers | Result | Where on this page |
|---|---|---|---|
| refs.geml · new.geml | A broken reference in another section, an edit to this one | 1 error Write refused | ids and reference checks |
| code.geml · src/math.js | Code taken by line range, the range stale | 1 error Stale range | Embedding and sync |
| doc.geml · complex-form.geml | list, list --within, find, get --head, rename; attribute filters | 0 diagnostics | Query and rewrite |
| samples.geml | How note, table, data and geml-chart are written | 0 diagnostics | Generic blocks · Components and extension points |