# Typst-side toolchain for `myform` (as of 2026-07-20) **Question:** What is the Python-accessible Typst analogue of the `markdown-it-py` / mdformat ecosystem `myform` already joins — for parsing `.typ` into an AST and for formatting/pretty-printing Typst source? **Thesis:** There is no such ecosystem to join, on either side. Nothing in the Typst world plays the role markdown-it-py/mdformat play for Markdown. **Emit** directly from `myform`'s own AST as hand-written Typst source strings (the same architecture `myform/Renderer.py` already uses for Markdown, and the same architecture `mystmd`'s official Typst exporter uses — this is confirmed convergent prior art, not a novel choice). **Format** by shelling out to the `typstyle` Rust binary as an optional external dependency, the same way one might shell out to `black` or `prettier`, because no Python-native or plugin-extensible Typst formatter exists. **Read/parse** `.typ` has no good Python option at all right now; treat it as future scope gated on either a maintained tree-sitter grammar reappearing or hand-rolling a minimal recursive-descent parser against `typst-syntax`'s node inventory as a spec. All claims below are dated 2026-07-20 (retrieval date); Typst itself is versioned 0.15.x pre-1.0, so all of this is subject to breaking change on any Typst point release. ## 1. Typst parsing from Python **No option produces a usable source-level AST inside a Python process today**, per a Typst community-forum thread devoted to exactly this question that concludes there is "no turnkey solution" and that `typst-py` is merely "the most mature effort" without actually exposing the AST — [Typst Forum: "How can I access the Typst AST from Python?"](https://forum.typst.app/t/how-can-i-access-the-typst-ast-from-python/6219) (2026-07-20; a forum thread is a weak source in isolation, but here it is corroborated by every primary source below, so I'm treating the *conclusion* as reliable even though the thread itself isn't citable for facts). - **`typst` PyPI package** ([PyPI](https://pypi.org/project/typst/), [GitHub messense/typst-py](https://github.com/messense/typst-py); v0.15.0, released 2026-06-16, actively maintained, 24 releases) is a PyO3 binding to the real Rust `typst` compiler. It exposes `compile()` (to PDF/PNG/SVG/HTML), a `Compiler` class, `query()`, and `eval()`. `query()` only returns "locatable elements of the *compiled* document" (post-layout content, i.e. the `Content`/element model, not source syntax); functions and macros aren't locatable this way. **It does not expose `typst-syntax`'s `SyntaxNode`/AST at all** — confirmed by the PyPI/GitHub descriptions and by the forum thread. Import cost is real: the Linux manylinux wheel is **34.2 MB** (PyPI file listing, retrieved 2026-07-20) because it embeds the full compiler, layout engine, and fonts — not appropriate for a formatter that must start fast. - **tree-sitter-typst grammars**: two independent, uncoordinated community grammars, neither current relative to Typst's own pace of change: - [`frozolotl/tree-sitter-typst`](https://github.com/frozolotl/tree-sitter-typst) — last commit **2024-01-29** (retrieved via commit history, 2026-07-20); explicitly lists missing coverage for "Code, Lists, Enums, Terms." - [`uben0/tree-sitter-typst`](https://github.com/uben0/tree-sitter-typst) — last commit **2025-02-10**; the maintainer's own README calls the scanner code "a spaghetti mess" awaiting a rewrite that (per the README) never shipped. - Neither publishes a Python wheel; both would need on-machine grammar compilation via `py-tree-sitter`'s build step. Generic `tree-sitter` core itself is light — **668 KB** manylinux wheel (PyPI, retrieved 2026-07-20), ~50x lighter than `typst-py` — so *if* a maintained grammar existed, this would be the right-weight answer. It doesn't. - Tellingly, Typst's own tooling (the `tinymist` LSP, `typstyle`) is built on the native `typst-syntax` Rust crate, not tree-sitter — tree-sitter-typst is a community side effort, not something the core project uses or feeds. - **No pure-Python Typst parser exists.** Search turned up only Python→Typst *emitters* (`pypst`, `typstpy`, `md2typst`; see §4/§5) and the `typst-py` compiler binding — no project that parses `.typ` source into a tree in pure Python. - **No published WASM build of `typst-syntax` alone.** [`Myriad-Dreamin/typst.ts`](https://github.com/Myriad-Dreamin/typst.ts) ("Run Typst in JavaScript World") ships WASM compiler/renderer modules on npm (`@myriaddreamin/typst-ts-web-compiler`) for JS/browser use, not Python, and compiles the whole engine, not a standalone syntax-tree module. ## 2. Typst formatting/pretty-printing **`typstyle` is the maintained formatter; `typstfmt` is dead.** - [`typstyle-rs/typstyle`](https://github.com/typstyle-rs/typstyle) — v0.15.0, released **2026-06-20** (retrieved 2026-07-20). Parses to AST via `typst-syntax`, formats with a Wadler-style pretty-printer (a fork of `pretty.rs` called `prettyless`), and ships correctness guarantees (idempotence + render-identical output tested against real repos). It is **opinionated by design and has no plugin/config-rule extensibility** — the maintainer's stated rationale is explicitly *against* the config-sprawl model mdformat plugins rely on. Distribution: CLI binary, a Rust library crate (`typstyle-core` on crates.io), and a WASM/npm build (`@typstyle/typstyle-wasm-bundler`) for JS. **No Python binding exists.** It's the formatter wired into `tinymist` (the standard Typst LSP/editor extension). - **`typstfmt` is abandoned, twice over**, and the community has already redirected to `typstyle`: - [`astrale-sharp/typstfmt`](https://github.com/astrale-sharp/typstfmt) (original) — archived **2024-06-08**; maintainer's own note points users to typstyle. - [`jeffa5/typstfmt`](https://github.com/jeffa5/typstfmt) (a rewrite of the above, aiming for more config flexibility) — archived **2025-12-11**, development moved to a private self-hosted git instance. - **Nothing mdformat-like exists in the Typst world** — i.e. no plugin-extensible formatter where third parties register custom node handlers into a shared pretty- printing pipeline. `typstyle` is a closed, opinionated Rust binary; there is no equivalent of mdformat's `Plugin`/extension-point model on the Typst side. ## 3. Typst AST / content-model specs Two distinct things exist, neither an external stability contract: - **Source syntax tree** — [`typst-syntax` crate](https://docs.rs/typst-syntax/latest/typst_syntax/) (v0.15.1): `SyntaxNode`/`SyntaxKind` (untyped tree), a typed `ast` module, `Source`/`Span`/`LinkedNode`. Reasonably documented (97% doc coverage per docs.rs) but its own top-of-file doc comment is a one-line "Parser and syntax tree for Typst" ([source](https://raw.githubusercontent.com/typst/typst/main/crates/typst-syntax/src/lib.rs), retrieved 2026-07-20) — no explicit stability/semver statement either way. Given Typst itself is pre-1.0 (0.15.x), treat every node name as liable to change on any release; that caution is an inference from the version number, not a quoted commitment from the crate. - **Content/document model** — [`typst.app/docs/reference/foundations/content/`](https://typst.app/docs/reference/foundations/content/) and [`typst.app/docs/reference/model/`](https://typst.app/docs/reference/model/) document the *runtime* `Content` value (elements + fields, compositional via `+`) and \~24 structural elements (heading, list, figure, table, bibliography, footnote, quote, etc.). This is a **post-compilation semantic model**, not a source-syntax spec, and it's documentation-by-reference-page rather than one canonical schema document. **Recommendation for node-type alignment:** use `typst-syntax`'s `SyntaxKind` enum (read via docs.rs, no install required) as the informal vocabulary to name `myform`'s Typst-facing AST nodes, and the `foundations/model` reference pages as the vocabulary for block/inline *semantic* categories (heading vs. list vs. figure) — but do not treat either as a frozen external contract; snapshot the version consulted. ## 4. Markdown↔Typst converters as prior art - **Pandoc**: writer is mature, reader is young. - Typst **writer** shipped in pandoc **3.1.2** (per web search corroborated by third-party Typst/pandoc integration repos noting they stopped maintaining custom extensions once "native support for Typst" landed) and has continued to receive fixes through 3.10 (released 2026-06-04, added `--typst-input` for `sys.inputs`). - Typst **reader** (`Text.Pandoc.Readers.Typst`) exists and evaluates `.typ` into the Pandoc AST, but its Hackage module doc explicitly marks stability as **"alpha"** ([Hackage pandoc-3.6.3 docs](https://hackage-content.haskell.org/package/pandoc-3.6.3/docs/Text-Pandoc-Readers-Typst.html), retrieved 2026-07-20) with no documented list of unsupported syntax — read this as "don't lean on it as a spec of correct Typst parsing yet." Two third-party custom reader/writer projects (`BroadbandForum/pandoc-typst-writer`, `lvignoli/typst-pandoc`) predate native support and are now superseded. - Architecturally, pandoc's Typst reader is a Haskell implementation independent of `typst-syntax` — it is not itself Python-accessible and wouldn't help `myform` directly even if more mature; it's useful as a second data point on what "supported Typst syntax" looks like for a document converter, not as a dependency. - **mystmd's Typst exporter is the most directly relevant prior art, and it validates the recommended architecture.** [`packages/myst-to-typst`](https://github.com/jupyter-book/mystmd/tree/main/packages/myst-to-typst) in the [mystmd](https://github.com/jupyter-book/mystmd) monorepo is a hand-written **visitor/handler pattern** (`TypstSerializer` class, `handlers` object keyed by mdast/MyST node type — confirmed by reading [`src/index.ts`](https://raw.githubusercontent.com/jupyter-book/mystmd/main/packages/myst-to-typst/src/index.ts), retrieved 2026-07-20) covering ~50 node types (paragraph, heading, emphasis/strong, code, math via `MATH_HANDLERS`, lists, tables, links, cross-references, citations, footnotes, images, admonitions, tabs, etc.). It **writes Typst markup as raw strings directly via `state.write()` — it imports no Typst-specific parsing/AST library at all.** This is exactly the architecture `myform`'s own `Renderer.py` already uses for Markdown, just aimed at a different output grammar. No published node-mapping spec document exists beyond the source itself; the README is a one-line stub ("Convert a MyST AST to Typst"). - **Minor Python-native prior art, emit-only, none general-purpose enough to build on:** - [`pypst`](https://krokotsch.eu/pypst/) ([GitHub tilman151/pypst](https://github.com/tilman151/pypst), latest release **2025-12-28**) — a declarative "build Typst objects in Python, get Typst source out" library, MIT-licensed. Covers headings, lists, tables (with strong Pandas-dataframe-to-table support as its actual headline use case), figures, images, imports. **No parsing capability, no math support, narrow node coverage** — it's closer to a report-generation DSL than a general Markdown-superset emitter. Worth knowing about, not worth depending on. - `typstpy` (PyPI) — functional-style Typst code generation, similarly emit-only and narrower in scope than what `myform` needs. - `md2typst` (PyPI) — Markdown→Typst string converter; another single-purpose converter, not a reusable AST/renderer library. ## 5. Recommendation **(a) EMIT path — build now, no new runtime dependency:** 1. Add a `Renderer`-style Typst emitter inside `myform`'s own AST→text pipeline, symmetric to the existing Markdown `Renderer.py`/`Postprocessor.py` split. This is the architecture both `mystmd`'s official exporter and `myform` itself already use — not a novel bet. 2. For final normalization/pretty-printing, shell out to the `typstyle` CLI binary as an **optional extra** (e.g. `myform[typst-format]` installs/requires the binary on `$PATH`, or vendor via the WASM/npm build if a pure-binary dependency is unwelcome). Do not attempt to reimplement typstyle's pretty-printing rules in Python; it has no plugin surface to extend anyway, so there's nothing to "join," only a binary to call. 3. Do **not** take a `typst-py` dependency for the emit path — 34 MB of compiler+font payload for output you're generating yourself is pure import-time cost with no benefit. **(b) READ path — defer, no good option today:** Ranked, worst-case-first: 1. **Best-available-but-unmaintained:** fork/revive `uben0/tree-sitter-typst` (more recently touched, 2025-02-10, but self-described as needing a rewrite) or `frozolotl/tree-sitter-typst` (closer to spec-correctness goal but stale since 2024-01-29 and missing whole node categories). Either requires real upstream investment before it's trustworthy, and neither has a Python wheel today — you'd own packaging via `py-tree-sitter`'s build step. 2. **Heaviest but most correct:** wrap `typst-syntax` directly via a small PyO3 binding (something `typst-py` does not currently expose per the forum thread) — this is "write the binding yourself," the community's own fallback suggestion, and the only option that tracks the real parser exactly. Costly in maintenance, not runtime weight (a thin binding need not carry the whole compiler/font payload `typst-py` does). 3. **Cheapest, narrowest:** hand-roll a minimal recursive-descent Typst parser in Python scoped only to the markup subset `myform` actually needs to round-trip (headings, emphasis, lists, links, math, code, tables) — explicitly not general Typst, using `typst-syntax`'s `SyntaxKind` names as the target vocabulary. Realistic if the read path only ever needs to ingest `myform`-authored or otherwise constrained `.typ` files, not arbitrary Typst in the wild. 4. Do not depend on pandoc's Typst reader for anything load-bearing; it's explicitly alpha and not Python-native anyway. **Follow-ups not chased here (in-scope-adjacent, out of this memo's bound):** (i) whether `typstyle`'s WASM build could be driven from Python via `wasmtime-py` instead of a subprocess call — worth a spike if subprocess overhead becomes a measured problem, not before; (ii) whether contributing fixes upstream to one of the two stale tree-sitter grammars is more efficient than a from-scratch PyO3 binding, once the read path is actually prioritized; (iii) re-check `typst-py` for AST exposure at its next release — the forum thread notes the original poster was considering contributing an "AST wrapper" themselves, so this could change without much warning. **Source-disagreement note:** none of substance — every source (forum thread, PyPI descriptions, GitHub repos, docs.rs, mystmd source) converges on the same conclusion: no Python-accessible Typst AST exists today. The only soft disagreement is stylistic — `typstfmt`'s now-archived README frames itself as more flexible/configurable than `typstyle`, while `typstyle`'s README frames flexibility as the wrong goal — but this is now moot since `typstfmt` is dead on both forks. This question was well-bounded to a single-charter survey (a handful of primary repos and docs, no adversarial fact-checking or compliance stakes); the `deep-research` skill is not warranted here.