markdown-it js library rewritten in rust
  • Rust 95.1%
  • JavaScript 2.4%
  • Python 2.1%
  • Kotlin 0.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
GSGFs 540334e275
Some checks are pending
Dependency audit / Audit release workspace dependencies (push) Waiting to run
minimal-dep-check-ignore-this / test (nightly) (push) Waiting to run
JS compatibility / Linkify crate compatibility (push) Waiting to run
JS compatibility / Differential (config 0) (push) Waiting to run
JS compatibility / Differential (config 1) (push) Waiting to run
JS compatibility / Differential (config 2) (push) Waiting to run
JS compatibility / Differential (config 3) (push) Waiting to run
JS compatibility / Differential (config 4) (push) Waiting to run
JS compatibility / Differential (config 5) (push) Waiting to run
Build Kotlin Binding / Build Native (android) (push) Waiting to run
Build Kotlin Binding / Build Native (aarch64-apple-darwin) (push) Waiting to run
Build Kotlin Binding / Build Native (x86_64-apple-darwin) (push) Waiting to run
Build Kotlin Binding / Build Native (x86_64-unknown-linux-gnu) (push) Waiting to run
Build Kotlin Binding / Build Native (aarch64-unknown-linux-gnu) (push) Waiting to run
Build Kotlin Binding / Build Native (x86_64-pc-windows-msvc) (push) Waiting to run
Build Kotlin Binding / Test and package JVM JAR (push) Blocked by required conditions
gh-pages / build (stable) (push) Waiting to run
gh-pages / deploy (push) Blocked by required conditions
Build Python Wheels / linux (aarch64) (push) Waiting to run
Build Python Wheels / linux (x86_64) (push) Waiting to run
Build Python Wheels / windows (3.10, x64) (push) Waiting to run
Build Python Wheels / windows (3.11, x64) (push) Waiting to run
Build Python Wheels / windows (3.12, x64) (push) Waiting to run
Build Python Wheels / windows (3.13, x64) (push) Waiting to run
Build Python Wheels / windows (3.14, x64) (push) Waiting to run
Build Python Wheels / windows (3.14t, x64) (push) Waiting to run
Build Python Wheels / windows (3.15, x64) (push) Waiting to run
Build Python Wheels / windows (3.15t, x64) (push) Waiting to run
Build Python Wheels / windows (pypy3.11-nightly, x64) (push) Waiting to run
Build Python Wheels / macos (map[runner:macos-15-intel target:x86_64], 3.10) (push) Waiting to run
Build Python Wheels / macos (map[runner:macos-15-intel target:x86_64], 3.11) (push) Waiting to run
Build Python Wheels / macos (map[runner:macos-15-intel target:x86_64], 3.12) (push) Waiting to run
Build Python Wheels / macos (map[runner:macos-15-intel target:x86_64], 3.13) (push) Waiting to run
Build Python Wheels / macos (map[runner:macos-15-intel target:x86_64], 3.14) (push) Waiting to run
Build Python Wheels / macos (map[runner:macos-15-intel target:x86_64], 3.14t) (push) Waiting to run
Build Python Wheels / macos (map[runner:macos-15-intel target:x86_64], 3.15) (push) Waiting to run
Build Python Wheels / macos (map[runner:macos-15-intel target:x86_64], 3.15t) (push) Waiting to run
Build Python Wheels / macos (map[runner:macos-15-intel target:x86_64], pypy3.11-nightly) (push) Waiting to run
Build Python Wheels / macos (map[runner:macos-latest target:aarch64], 3.10) (push) Waiting to run
Build Python Wheels / macos (map[runner:macos-latest target:aarch64], 3.11) (push) Waiting to run
Build Python Wheels / macos (map[runner:macos-latest target:aarch64], 3.12) (push) Waiting to run
Build Python Wheels / macos (map[runner:macos-latest target:aarch64], 3.13) (push) Waiting to run
Build Python Wheels / macos (map[runner:macos-latest target:aarch64], 3.14) (push) Waiting to run
Build Python Wheels / macos (map[runner:macos-latest target:aarch64], 3.14t) (push) Waiting to run
Build Python Wheels / macos (map[runner:macos-latest target:aarch64], 3.15) (push) Waiting to run
Build Python Wheels / macos (map[runner:macos-latest target:aarch64], 3.15t) (push) Waiting to run
Build Python Wheels / macos (map[runner:macos-latest target:aarch64], pypy3.11-nightly) (push) Waiting to run
Build Python Wheels / sdist (push) Waiting to run
tests / Format and Clippy (push) Waiting to run
tests / Test (default features) (push) Waiting to run
tests / Test (all features) (push) Waiting to run
tests / Test (no default features) (push) Waiting to run
refactor(linkify): rewrite linkify crate
2026-10-10 23:53:10 +08:00
.github/workflows refactor(linkify): rewrite linkify crate 2026-10-10 23:53:10 +08:00
benchmarks perf(render): accelerate escaping of long plain HTML text 2026-10-09 22:54:34 +08:00
bindings refactor: reorganize parser, document, and shared modules 2026-10-09 10:59:04 +08:00
crates refactor(linkify): rewrite linkify crate 2026-10-10 23:53:10 +08:00
demo refactor(extra): remove GFM alert plugin 2026-10-07 22:41:07 +08:00
examples test(differential): add JS compatibility harness against markdown-it 2026-10-09 23:46:11 +08:00
src refactor(linkify): rewrite linkify crate 2026-10-10 23:53:10 +08:00
tests fix: classify NUL as replacement character in emphasis 2026-10-10 00:50:02 +08:00
.gitignore test(differential): add JS compatibility harness against markdown-it 2026-10-09 23:46:11 +08:00
Cargo.lock refactor(linkify): rewrite linkify crate 2026-10-10 23:53:10 +08:00
Cargo.toml test(differential): add JS compatibility harness against markdown-it 2026-10-09 23:46:11 +08:00
CHANGELOG.md refactor(parser): share source storage between Root and Document 2026-10-08 20:57:02 +08:00
LICENSE chore: update LICENSE 2026-08-29 16:02:43 +08:00
README.md docs: update readme 2026-08-31 18:33:35 +08:00
rustfmt.toml feat(syntect): support highlighted code fence lines 2026-04-23 14:17:40 +08:00
THIRD_PARTY_LICENSES feat(plugin): add CJK-friendly delimiter handling 2026-08-29 16:02:23 +08:00

markdown-it-rs

Note

This is a personally maintained fork of markdown-it-rust/markdown-it.

Warning

Status: unpublished 1.0.0 — contains many breaking changes compared to the latest published version (0.7.0); the API is not stable yet.

A Rust-native, AST-first Markdown parser with markdown-it.js-compatible rendering.

You can check a demo in your browser.

Features

  • 100% CommonMark compatible & 100% markdown-it.js-compatible HTML rendering
  • Mutable, typed AST
  • Everything is a plugin
  • Source maps for parsed nodes
  • Extensible core, block, and inline rule chains
  • Optional CJK-friendly emphasis handling
  • Optional Python and WebAssembly bindings

Quick start

The MarkdownItDefault preset corresponds to markdown-it.js's default syntax:

use markdown_it::{MarkdownIt, Preset};

fn main() {
    let md = MarkdownIt::with_preset(Preset::MarkdownItDefault);
    let html = md.render("Hello **world**!");

    assert_eq!(html, "<p>Hello <strong>world</strong>!</p>\n");
}

Assembling your own Rust Markdown dialect:

use markdown_it::MarkdownIt;
use markdown_it::plugins::{cmark, extra};

fn main() {
    let mut md = MarkdownIt::empty();
    cmark::add(&mut md);
    extra::tables::add(&mut md);
    extra::tasklist::add(&mut md);
    extra::footnote::add(&mut md);
    // ...
}

Planned plugin API

Important

This section is a design target for the next major version, not an API that is available in the current release yet.

The planned high-level API uses typed, composable builders for common plugins, while keeping the lower-level rule and AST interfaces available for advanced parsers. An emoji shortcode plugin with custom HTML rendering should look roughly like this:

use markdown_it::{MarkdownIt, PluginSpec};

const EMOJIS: &[(&str, &str, &str)] = &[
    (":rocket:", "🚀", "rocket"),
    (":warning:", "⚠️", "warning"),
];

#[derive(Debug)]
struct Emoji {
    glyph: &'static str,
    label: &'static str,
}

fn emoji() -> PluginSpec {
    PluginSpec::new("emoji")
        .inline_leaf::<Emoji>("shortcode")
        .marker(':')
        .parse(|cx| {
            let &(shortcode, glyph, label) = EMOJIS
                .iter()
                .find(|(code, _, _)| cx.starts_with(code))?;

            cx.consume(shortcode);
            Some(Emoji { glyph, label })
        })
        .render_html(|emoji, html| {
            html.element("span")
                .class("emoji")
                .attr("role", "img")
                .attr("aria-label", emoji.label)
                .text(emoji.glyph);
        })
        .finish()
}

let md = MarkdownIt::builder().plugin(emoji()).build()?;
let html = md.render("Ready :rocket:");
//assert_eq!(html, r#"<p>Ready <span class="emoji" role="img" aria-label="rocket">🚀</span></p>"#)

The goal is to keep simple plugins around 20 lines, with automatic rollback and HTML escaping, while retaining lower-level APIs for advanced plugins.

Until then, see the examples/ferris folder for a detailed guide to the current low-level plugin API.

CJK-friendly delimiters

The optional cjk_friendly plugin implements the delimiter amendments from markdown-cjk-friendly, so emphasis next to Chinese, Japanese, or Korean punctuation works without spaces:

use markdown_it::{MarkdownIt, Preset};

let mut md = MarkdownIt::with_preset(Preset::MarkdownItDefault);
markdown_it::plugins::cjk_friendly::add(&mut md);

assert_eq!(
    md.render("**这是重要内容。**后面可以继续写~~,被删除的内容~~"),
    "<p><strong>这是重要内容。</strong>后面可以继续写<s>,被删除的内容</s></p>\n"
);

Security

This lib does not sanitize or filter any HTML output. You should add a sanitizer before rendering untrusted content.

There are two plugins you should be careful with:

  • html - enable raw inline/block HTML. By default plugins::cmark does not enable raw HTML. Add markdown_it::plugins::html::add(parser) to enable it.

  • directives - allows custom directives like :name{key=value} that are rendered by user provided content. The default renderers simply emit <span>/<div> wrappers. But it might be used like :name{onclick=...}.