kotmark

A Markdown engine
Kotlin Multiplatform

Guide / configuration

Configuration

Parsers and renderers are created by Kotlin factory functions with a configuration block. Omitting the block uses the defaults.

Example

Build one extension list and pass it to the parser and renderer. Each extension is created once, with the same settings used on both sides.

Share configured extensions
import io.heapy.kotmark.ext.gfm.strikethrough.strikethrough
import io.heapy.kotmark.ext.gfm.tables.tables
import io.heapy.kotmark.extensions
import io.heapy.kotmark.parser.parser
import io.heapy.kotmark.renderer.html.htmlRenderer

fun renderWithExtensions(markdown: String): String {
    val extensions = extensions {
        tables { maxCells = 100_000 }
        strikethrough { requireTwoTildes = true }
    }
    val parser = parser { extensions(extensions) }
    val renderer = htmlRenderer { extensions(extensions) }
    return renderer.render(parser.parse(markdown))
}

Extension lists

Import io.heapy.kotmark.extensions for the list builder and registration functions such as tables from the corresponding extension packages. Each function is provided by its optional module; see the extension index.

Use add(customExtension) to register an existing extension, and addAll(existingExtensions) to compose a list from another collection:

Compose extension lists
import io.heapy.kotmark.Extension
import io.heapy.kotmark.ext.gfm.tables.tables
import io.heapy.kotmark.extensions

fun combineExtensions(
    customExtension: Extension,
    existingExtensions: Iterable<Extension>,
): List<Extension> = extensions {
    add(customExtension)
    addAll(existingExtensions)
    tables { maxCells = 10_000 }
}

The builder returns a read-only List<Extension> snapshot. It preserves insertion order and duplicates; later changes to the builder or source collections do not change the list. Extension objects and callbacks remain shared, so any mutable state they hold is the caller's responsibility.

Register custom renderer overrides before extensions that supply default handlers: the first registered renderer for a node type wins. Parsers and renderers use only the extension interfaces they support, so a shared list can also contain parser-only or renderer-only extensions. The kotlinx.html renderer uses its own adapters for extension output.

Factories

Each factory creates an independent configuration. The core provides factories for Parser, HtmlRenderer, MarkdownRenderer, and TextContentRenderer; optional modules add their own extension factories.

Typed node renderers

Use nodeRenderer<Heading> to register a handler whose node argument already has the correct type. Register overrides before the extension that supplies the default renderer.

Block nesting limit

maxOpenBlockParsers defaults to 100. Once the limit is reached, additional block starts are treated as plain text instead of creating deeper block structure. The document root is not counted, and reaching the limit does not throw an exception.

Set parser { maxOpenBlockParsers = Int.MAX_VALUE } to remove the limit, or choose another non-negative value. This option controls block nesting; it does not limit inline nesting or the total number of blocks in a document.

Inline nesting limit

maxInlineNesting defaults to 100. It tracks combined nesting from emphasis-like delimiters, bracketed links, and images. Markers that would exceed the limit remain plain text. Set parser { maxInlineNesting = 3 } to choose another non-negative limit, or use Int.MAX_VALUE to disable it.

Emphasis inside a link is processed after the link's limit check, so that combination can exceed the configured limit by one level. Custom delimiter processors participate when they insert their new node directly after the opener; arbitrary subtrees supplied by custom extensions are not fully measured.

Disabling the limit permits deep trees that recursive renderers and visitors may not be able to traverse.

Configuration rules

  • Register extensions with the parser and with each renderer that should handle them.
  • Factory functions snapshot options and registrations when they create an instance.
  • Registered callback objects remain shared; their mutable state is the caller's responsibility.