Core Concepts

Parsing

Parsing

Parsing is synchronous and deterministic:

ts
import { parseMarkdown } from '@tanstack/markdown/parser'

const document = parseMarkdown(source, {
  frontmatter: true,
  headingIds: true,
})

Input normalization

The parser normalizes line endings before extracting frontmatter, link definitions, footnote definitions, blocks, and inline content. It does not read files, load plugins, or perform asynchronous work.

Frontmatter

A leading --- block is extracted by default:

markdown
---
title: Small Markdown
published: true
---

# Article

document.frontmatter contains the unparsed text between delimiters. Set frontmatter: false to treat the input as ordinary Markdown.

Heading IDs

Headings receive lowercase, duplicate-safe IDs by default. Disable them or provide a custom function:

ts
parseMarkdown(source, { headingIds: false })

parseMarkdown(source, {
  headingIds(text, sourceLine) {
    return `${sourceLine}-${text.toLowerCase().replaceAll(' ', '-')}`
  },
})

The second callback argument is the zero-based normalized parser line index. It is not a source-location API because frontmatter and definitions may already have been extracted.

Definitions and footnotes

Link definitions are collected before block parsing and resolved case-insensitively. Footnotes are emitted in first-reference order, with collision-safe IDs and back-reference counts.

The references, footnotes, footnoteOrder, and footnoteCounts fields in ParseOptions carry parser state through nested parsing. Most consumers should leave them unset.

Extensions

Extensions may claim a block, transform inline nodes, transform the completed document, or render custom HTML. They run in array order. See Extensions for lifecycle and examples.

Complexity bounds

Block recursion is capped, inline scanning uses explicit budgets, and malformed-input behavior is covered by fixed-seed fuzzing and adversarial runtime tests. Inputs beyond a parser depth limit degrade to text content instead of recursing without bound.