Markdown to Typst converter with multiple parser support
Project description
md2typst
A robust Markdown to Typst converter in Python with support for multiple Markdown parsers.
Features
- Multiple parser backends: Choose from markdown-it-py, mistune, or marko at runtime
- GFM support: Tables, strikethrough, footnotes, and other GitHub Flavored Markdown extensions
- Math support:
$...$and$$...$$rendered via mitex, enabled by default - Mermaid diagrams:
```mermaidcode blocks rendered via mmdr - Auto-imports: Required Typst packages are automatically imported based on content
- Direct PDF output:
md2pdfcommand converts Markdown to PDF in one step (requirestypstCLI) - Document classes:
article,report, andbookpresets (like LaTeX), selectable via front matter, CLI, or config - Title blocks: Automatic title page/block from front matter (title, author, date, version, publisher)
[TOC]support: Table of contents via[TOC]marker- Diagram blocks:
```diagramkeeps ASCII-art diagrams from breaking across pages - Configurable: User config, project config, YAML front matter, and CLI options with clear cascade
- Extensible: Plugin support for parser-specific extensions
- Well-tested: Comprehensive test suite with TCK validation against CommonMark
Installation
# Using pip
pip install md2typst
# Using uv
uv add md2typst
Quick Start
Command Line
# Convert a file (writes input.typ by default)
md2typst input.md
# Explicit output path
md2typst input.md -o output.typ
# Output to stdout
md2typst input.md -o -
# Convert from stdin (outputs to stdout)
echo "# Hello **World**" | md2typst
# Use a specific parser
md2typst --parser mistune input.md
# Convert multiple files at once
md2typst *.md
md2pdf *.md
# Convert directly to PDF (requires typst CLI)
md2pdf input.md
md2pdf input.md -o custom-output.pdf
# Use a document class
md2pdf --class report input.md
# List available parsers
md2typst --list-parsers
Python API
from md2typst import convert
# Simple conversion
typst = convert("# Hello **World**")
print(typst)
# Output: = Hello *World*
# With specific parser
typst = convert("~~deleted~~", parser="mistune")
print(typst)
# Output: #strike[deleted]
# With configuration
from md2typst import convert_with_config
from md2typst.config import Config
config = Config(parser="marko", plugins=["gfm"])
typst = convert_with_config("| A | B |\n|---|---|\n| 1 | 2 |", config)
Supported Parsers
| Parser | CLI Name | Description |
|---|---|---|
| markdown-it-py | markdown-it |
Default. CommonMark compliant, extensible |
| mistune | mistune |
Fast, pure Python |
| marko | marko |
CommonMark compliant, extensible |
All parsers have GFM extensions (tables, strikethrough, footnotes) enabled by default.
Markdown to Typst Mapping
| Markdown | Typst |
|---|---|
# Heading |
= Heading |
## Heading 2 |
== Heading 2 |
*italic* |
_italic_ |
**bold** |
*bold* |
~~strike~~ |
#strike[strike] |
`code` |
`code` |
[text](url) |
#link("url")[text] |
 |
#image("url", alt: "alt") |
> quote |
#block(...)[quote] |
$E=mc^2$ |
#mi("E=mc^2") |
$$...\int...$$ |
#mitex(\...`)` |
--- |
#line(length: 100%) |
| GFM tables | #table(...) |
text[^1] / [^1]: note |
text#footnote[note] |
```mermaid |
#mermaid("...") |
```diagram |
#block(breakable: false)[...] |
[TOC] |
#outline(indent: auto, depth: 4) |
Configuration
Configuration is loaded from multiple sources (highest priority first):
- CLI arguments (
--parser,--plugin, ...) - Front matter in the document
- Explicit config file (
--config path/to/config.toml) md2typst.tomlin the current or parent directories[tool.md2typst]section inpyproject.toml- User config at
~/.config/md2typst/config.toml(platform-specific viaplatformdirs) - Built-in defaults
Styling with [style]
The [style] section customizes Typst's default output. Set your preferred font, language, and page layout once in your user config — applied to every document you convert.
~/.config/md2typst/config.toml:
[style]
# Font as single string or a fallback list
font = ["Libertinus Serif", "New Computer Modern", "Times New Roman"]
font_size = "11pt"
language = "en"
paper = "a4"
margin = "2.5cm"
# Raw Typst code appended after structured fields
preamble = """
#set par(justify: true, first-line-indent: 1em)
#show heading.where(level: 1): it => { it; v(0.5em) }
"""
This generates at the top of every converted document:
#set text(font: ("Libertinus Serif", "New Computer Modern", "Times New Roman"), size: 11pt, lang: "en")
#set page(paper: "a4", margin: 2.5cm)
#set par(justify: true, first-line-indent: 1em)
#show heading.where(level: 1): it => { it; v(0.5em) }
Example Configuration
md2typst.toml (project-level, overrides user config):
parser = "mistune"
plugins = ["strikethrough", "table"]
[parser_options]
html = true
[style]
language = "fr"
pyproject.toml:
[tool.md2typst]
parser = "markdown-it"
plugins = ["gfm"]
Front Matter
Markdown files can include YAML front matter. Any field is exposed as a #let doc-<key> variable in Typst, except reserved keys with special handling:
preamble— raw Typst code, concatenated with configstyle.preamblestylesheet/stylesheets— additional Typst modules to importfont,font_size,language,paper,margin— override the corresponding[style]fields at document level
---
title: My Document
author: Jane Doe
language: fr # overrides config style.language
font: EB Garamond # overrides config style.font
stylesheet: my-style
preamble: |
#set par(justify: true)
#show heading.where(level: 1): it => { it; v(0.5em) }
---
# Hello World
This generates:
#let doc-title = "My Document"
#let doc-author = "Jane Doe"
#import "my-style.typ": *
#set text(font: "EB Garamond", lang: "fr")
#set par(justify: true)
#show heading.where(level: 1): it => { it; v(0.5em) }
= Hello World
The output ordering is: variables, stylesheet imports, package imports, #set directives from [style], preamble, then content.
Math
Dollar-sign math syntax is enabled by default (markdown-it and mistune parsers). The mitex package import is added automatically when math is detected.
Inline: $E = mc^2$
Display:
$$
\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}
$$
Mermaid Diagrams
Fenced code blocks with language mermaid are converted to native Typst diagrams using the mmdr package. The import is added automatically.
```mermaid
graph LR
A[Start] --> B{Decision}
B -->|Yes| C[OK]
B -->|No| D[End]
```
Diagram Blocks
Use ```diagram for ASCII-art or box-drawing diagrams that must not break across pages:
```diagram
┌──────────┐ ┌──────────┐
│ Client │──────▶│ Server │
└──────────┘ └──────────┘
```
The content is wrapped in #block(breakable: false) in the Typst output.
Document Classes
md2typst supports named document classes inspired by LaTeX (article, report, book). Each class provides a complete set of Typst styling rules.
Select a class via front matter, CLI, or config:
---
class: report
title: My Report
---
md2pdf --class book input.md
# In md2typst.toml or ~/.config/md2typst/config.toml
default_class = "article"
| Class | Title | Sections | Page breaks | TOC |
|---|---|---|---|---|
| article | Inline on first page | Numbered, no breaks | None | Inline |
| report | Centered with rule | Numbered (skip h1), breaks before ## |
Before sections | Own page |
| book | Full title page | "Chapter N" for h1, numbered | Chapters on odd pages | Odd page |
Classes are defined in [classes.<name>] config sections. The class preamble replaces the base [style] preamble; scalar fields (font, paper, etc.) are inherited unless overridden.
Title Block
When title metadata is present in front matter, a title block is automatically generated. Each document class formats it differently.
---
title: My Document
subtitle: A Technical Overview
author: Jane Doe
date: April 2026
version: "1.0"
publisher: Acme Corp
---
Supported fields: title, subtitle, author, authors, date, version, publisher. Classes define a doc-make-title() function in their preamble to customize the title formatting.
Table of Contents
Place [TOC] on its own line to generate a table of contents:
[TOC]
## Introduction
...
CLI Options
md2typst --help
Options:
-o, --output FILE Output file (default: <input>.typ, or stdout for stdin)
-p, --parser NAME Parser to use (markdown-it, mistune, marko)
--class NAME Document class (article, report, book)
--plugin NAME Load parser plugin (can be repeated)
--stylesheet NAME Import Typst stylesheet (can be repeated)
--config FILE Path to configuration file
--list-parsers List available parsers
--show-config Show effective configuration
--debug Show debug info (config sources, generated Typst)
Development
Setup
git clone https://github.com/user/md2typst.git
cd md2typst
uv sync
Running Tests
# Run all tests (benchmarks skipped by default)
uv run pytest
# Run by category
uv run pytest -m unit # Unit tests (fast)
uv run pytest -m integration # Integration tests
uv run pytest -m e2e # End-to-end tests
uv run pytest -m benchmark # Benchmark tests
Test Structure
tests/
├── a_unit/ # Unit tests (AST, generator)
├── b_integration/ # Integration tests (parsers, config, TCK)
├── c_e2e/ # End-to-end tests
├── d_benchmark/ # Performance benchmarks
└── fixtures/ # Test fixtures (CommonMark, GFM)
Sample Documents
The samples/ directory contains example documents for each class with a local config and Makefile:
cd samples
make all # Build article.pdf, report.pdf, book.pdf
make clean # Remove generated files
Code Quality
# Type checking
uv run mypy src/
# Linting
uv run ruff check src/
# Formatting
uv run ruff format src/
Architecture
Markdown Input → Parser → AST → Generator → Typst Output
The converter uses a parser-agnostic AST (Abstract Syntax Tree) that decouples parsing from code generation. This allows:
- Swapping parsers without changing the generator
- Consistent output regardless of parser choice
- Easy extension with new parsers
License
MIT
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file md2typst-0.3.3.tar.gz.
File metadata
- Download URL: md2typst-0.3.3.tar.gz
- Upload date:
- Size: 28.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.1 {"installer":{"name":"uv","version":"0.11.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9cb119bdd438f64bae3e416641f69e34e53f7eeee0dea41d378f89e1d16e267e
|
|
| MD5 |
66dbda837f186f7a114ebc5c800252d3
|
|
| BLAKE2b-256 |
cc527b8b34bee9726af54301710e04109cd2f5ba63cc4afd2bf5f07253435bf4
|
File details
Details for the file md2typst-0.3.3-py3-none-any.whl.
File metadata
- Download URL: md2typst-0.3.3-py3-none-any.whl
- Upload date:
- Size: 35.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.1 {"installer":{"name":"uv","version":"0.11.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
268216b999615136e533aaaefb9161d6d21e6aafd7a8fa3496dcffbef8310d85
|
|
| MD5 |
53c85777faa1a1f9464edb5040475417
|
|
| BLAKE2b-256 |
754cbdab6fcdf06b48d992defb01cd024921fda5af0c3cf4c2594e2113998576
|