Migrating from Mistune

Replace Mistune helpers, plugins, AST usage, and renderer subclasses with Wenmode presets, explicit rules, node transforms, and renderer handlers.


Mistune and Wenmode share design DNA, but their extension models are different. Mistune centers on Markdown instances, plugins, and renderers. Wenmode centers on explicit parser rules, mdast-compatible nodes, document transforms, and renderer dispatch.

Simple HTML rendering

Mistune’s convenience helper converts Markdown directly to HTML:

mistune
import mistune

html = mistune.html(text)

The closest Wenmode replacement is:

wenmode
from wenmode import Wenmode

html = Wenmode().render(text)

Reusable parser instances

Mistune applications often create a reusable Markdown instance:

mistune
import mistune

markdown = mistune.create_markdown(renderer='html', plugins=['table'])
html = markdown(text)

In Wenmode, keep a reusable Wenmode object:

wenmode
from wenmode import Wenmode
from wenmode.presets import github

wenmode = Wenmode(github)
html = wenmode.render(text)

Parser state is created per parse, so definitions, footnotes, abbreviations, and deferred inline queues do not leak between calls.

Plugin and GFM setup

Choose github when your Mistune use relied on GFM-like features such as tables, task list items, strikethrough, extended autolinks, or footnotes:

mistune
import mistune

markdown = mistune.create_markdown(
    plugins=['table', 'strikethrough', 'footnotes', 'url'],
)
html = markdown(text)
wenmode
from wenmode import Wenmode
from wenmode.presets import github

wenmode = Wenmode(github)
html = wenmode.render(text)

For a smaller dialect, start from commonmark and add only the Wenmode rules that correspond to the Mistune plugins you actually used.

HTML safety behavior

Mistune configurations are often used as direct HTML filters. When migrating, check whether raw HTML was supposed to pass through.

If the old integration intentionally allowed raw HTML, it often looked like this:

mistune
import mistune

markdown = mistune.create_markdown(escape=False)
html = markdown(text)

Use Wenmode raw HTML passthrough only for trusted or separately sanitized input:

wenmode
from wenmode import HTMLRenderer, Wenmode

wenmode = Wenmode(renderer=HTMLRenderer(escape=False))
html = wenmode.render(text)

Wenmode’s default HTMLRenderer() escapes raw HTML and sanitizes unsafe URLs, so keep the default renderer for user-authored content.

To keep raw HTML syntax as text in the AST, remove HtmlBlock and RawHtml from the rule list instead of relying only on renderer escaping.

Plugin mapping

Mistune plugins do not map one-to-one to Wenmode APIs. Use this table as a starting point:

Mistune behavior

Wenmode replacement

table

github preset or Table rule

strikethrough

github preset or Strikethrough rule

footnotes

github preset or Footnote rule

url / bare autolinks

github preset or ExtendedAutolink rule

custom inline plugin

custom InlineRule

custom block plugin

custom BlockRule or ContinueRule

plugin state

StateKey and BlockState.store

renderer plugin

renderer handler registered with HTMLRenderer.register() or another renderer class

AST migration

If you used Mistune’s AST renderer:

mistune
import mistune

markdown = mistune.create_markdown(renderer='ast')
tokens = markdown(text)

Migrate code to Wenmode nodes:

wenmode
from wenmode import Wenmode

root = Wenmode().parse(text)
payload = root.to_ast()

Wenmode’s to_ast() output uses mdast-style dictionaries where possible: root, paragraph, heading, text, link, image, code, html, and extension node types such as table, footnoteReference, and directives.

For code that previously walked Mistune tokens, prefer direct node traversal when you need Python objects, or root.to_ast() when you need serializable data.

Renderer migration

Mistune custom renderers:

mistune
import mistune


class MyRenderer(mistune.HTMLRenderer):
    def text(self, text: str) -> str:
        return mistune.escape(text)


markdown = mistune.create_markdown(renderer=MyRenderer())
html = markdown(text)

Become Wenmode renderer handlers:

wenmode
from wenmode import HTMLRenderer
from wenmode.nodes import Text
from wenmode.renderers import RenderContext


@HTMLRenderer.register('text')
def render_text(renderer: HTMLRenderer, node: Text, context: RenderContext) -> str:
    return renderer.escape_html(node.value)

For custom node types, define the node in your extension and register handlers for every output format you support. See Custom rules for a complete custom node example.

Checklist

  • Pick commonmark or github before porting individual plugins.

  • Decide whether raw HTML should be escaped or passed through.

  • Replace Mistune plugins with rules, transforms, directive renderers, or renderer handlers.

  • Compare generated HTML for real documents, especially tables, footnotes, raw HTML, and autolinks.

  • Update tests that asserted Mistune token shapes to assert Wenmode nodes or to_ast() dictionaries instead.