TanStack
Core Concepts

Document Model

Document Model

parseMarkdown returns a MarkdownDocument: a plain, serializable object with block nodes under children.

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

export default function render(output: HTMLElement) {
  output.innerHTML = `
  <style>
    .ast-explorer { display: grid; gap: 12px; }
    .ast-explorer textarea,
    .ast-explorer pre {
      box-sizing: border-box;
      width: 100%;
      margin: 0;
      border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
      border-radius: 8px;
      background: var(--notebook-background);
      color: var(--notebook-foreground);
      font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
    }
    .ast-explorer textarea { min-height: 90px; padding: 12px; resize: vertical; }
    .ast-explorer pre { max-height: 300px; overflow: auto; padding: 12px; }
  </style>
  <main class="ast-explorer">
    <textarea aria-label="Markdown source"># Hello **world**</textarea>
    <pre aria-label="Parsed Markdown document"></pre>
  </main>
  `

  const source = output.querySelector<HTMLTextAreaElement>('textarea')
  const ast = output.querySelector<HTMLElement>('pre')
  if (!source || !ast) throw new Error('AST explorer controls not found')

  function update() {
    ast.textContent = JSON.stringify(parseMarkdown(source.value), null, 2)
  }

  source.addEventListener('input', update)
  update()
}

Block and inline nodes

Block nodes describe document structure: headings, paragraphs, code, lists, blockquotes, tables, footnotes, thematic breaks, HTML, callouts, and extension components.

Inline nodes describe text flow: text, inline code, emphasis, strong text, strike, links, images, breaks, footnote references, and inline HTML.

Every node has a discriminating type, so TypeScript narrows the union naturally:

ts
for (const block of document.children) {
  if (block.type === 'heading') {
    console.log(block.depth, block.id)
  }
}

The complete node contracts are in the Types Reference.

Derived document data

Frontmatter is stored as the original string in document.frontmatter. TanStack Markdown does not choose a YAML parser or schema for the application.

The heading collection extension adds document.headings, a compact array of IDs, text, levels, and optional framework labels.

Parse once, render many

All renderers accept a source string or a MarkdownDocument:

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

const document = parseMarkdown(source)

await cache.set(key, JSON.stringify(document))
const html = renderHtml(document)

Parsing once is useful for build pipelines, content indexes, multiple render targets, and high-traffic SSR paths. Rendering a pre-parsed AST is also the fastest measured path in the package.

Stability

The AST is public and typed, but the package is still pre-1.0. Pin versions when persisting documents across deployments, and regenerate cached AST data when upgrading across a release that changes node contracts.