Markdown to a typed AST
your front-end can render.
markast parses Markdown into a typed, structured tree. Everything happens in the library — parsing, validation, traversal, rendering. Ship the JSON to any client and switch on type.
What you get
Typed AST
Every node has a type discriminator and known fields. Walk it, query it, mutate it — without writing a single regex.
Pluggable widgets
Author :::widget components in Markdown that survive into the AST as structured nodes with typed props and named slots.
Never crashes
Bad input produces a diagnostic, not an exception. parse always returns a valid AST so your CMS keeps shipping.
Three output formats
Roundtrip back to Markdown, render to HTML server-side, or ship as JSON. Three single-method calls.
Transform pipeline
Slugify headings, build a TOC, autolink URLs, normalise text spans — chain them, write your own.
Front-end agnostic
Native mobile, React/Vue/Svelte, terminal, plain HTML — anything that can switch on a string.
30-second tour
pip install markast
from markast import parse
doc = parse("""
# Welcome
A paragraph with **bold** text and a [link](https://example.com).
:::tip title="Pro tip"
Markdown still works inside widgets.
:::
""")
doc.to_json() # str — ship to any client
doc.to_markdown() # str — roundtrip
doc.to_html() # str — server-side render
Why a tree, not HTML?
HTML is a one-way street. Once your content is rendered, the structure is gone — clients can't selectively style headings differently per platform, can't replace a :::video with a native player, can't extract a TOC without re-parsing. A typed AST keeps the meaning intact:
- Mobile apps render headings with their own typography rules.
- The web renders
:::videoas a custom player; the terminal renders it as a link. - Search indexes the same structured nodes that drive the UI.
- The same content powers a docs site, a CMS preview, and a CLI — without re-authoring.