Render API
renderMarkdown(tree, options?)
Converts a ComarkTree back into a markdown string, preserving frontmatter, component syntax, and attributes.
Parameters:
tree- TheComarkTreereturned byparse()orcreateParse()options?- Optional render options
Returns: Promise<string> — the serialized markdown string
Example:
import { parse } from 'comark'
import { renderMarkdown } from 'comark/render'
const tree = await parse(`---
title: Hello
---
# Hello **World**
::alert{type="info"}
This is an alert
::
`)
const markdown = await renderMarkdown(tree)---
title: Hello
---
# Hello **World**
::alert{type="info"}
This is an alert
::Options
| Option | Type | Default | Description |
|---|---|---|---|
maxInlineAttributes | number | 3 | Maximum attributes before switching to YAML block syntax. Set to 0 to always use block syntax |
blockAttributesStyle | 'frontmatter' | 'codeblock' | 'codeblock' | Syntax style for block attributes when they exceed maxInlineAttributes |
frontmatterOptions | DumpOptions | { indent: 2, lineWidth: -1 } | js-yaml DumpOptions passed to the frontmatter serializer |
components | Record<string, NodeHandler> | {} | Custom render handlers for specific elements |
data | Record<string, any> | {} | Additional data passed to render handlers |
maxInlineAttributes
When a component has more attributes than maxInlineAttributes, Comark switches to YAML block syntax. The blockAttributesStyle option controls which block format is used:
// Renders as inline when 3 or fewer attributes
// ::card{title="Hello" icon="star" color="blue"}
// Switches to block syntax when more than 3::card
```yaml [props]
title: Hello
icon: star
color: blue
size: large
```
::::card
---
title: Hello
icon: star
color: blue
size: large
---
::Switch between styles with the blockAttributesStyle option:
// Use codeblock style (default)
const md = await renderMarkdown(tree, { blockAttributesStyle: 'codeblock' })
// Use frontmatter style
const md = await renderMarkdown(tree, { blockAttributesStyle: 'frontmatter' })
// Always use block syntax with frontmatter style
const md = await renderMarkdown(tree, {
maxInlineAttributes: 0,
blockAttributesStyle: 'frontmatter',
})frontmatterOptions
By default frontmatter strings are never line-wrapped (lineWidth: -1). Pass any js-yaml DumpOptions via frontmatterOptions to override this or control other serialization behaviour:
const md = await renderMarkdown(tree, {
frontmatterOptions: { lineWidth: 80, sortKeys: true },
})components
Override how specific elements are serialized back to markdown. Each handler receives the node and a context object with a render helper for recursing into children.
import { parse } from 'comark'
import { renderMarkdown } from 'comark/render'
const tree = await parse(source)
const markdown = await renderMarkdown(tree, {
components: {
alert: ([, attrs, ...children], { render }) => {
return `::alert{type="${attrs.type}"}\n${render(children)}\n::`
}
}
})data
Additional data passed to every component handler via the context object. Useful for sharing configuration or state across handlers:
const markdown = await renderMarkdown(tree, {
data: { baseUrl: 'https://example.com' },
components: {
a: ([, attrs, ...children], { render, data }) => {
const href = attrs.href?.startsWith('/')
? `${data.baseUrl}${attrs.href}`
: attrs.href
return `[${render(children)}](${href})`
}
}
})Use Cases
Round-trip Transformation
Parse markdown, programmatically transform the AST, then serialize back:
import { parse } from 'comark'
import { renderMarkdown } from 'comark/render'
import { visit } from 'comark/utils'
const tree = await parse(source)
// Add an "external" class to all links
visit(tree,
(node) => Array.isArray(node) && node[0] === 'a',
(node) => {
const el = node as [string, Record<string, any>, ...any[]]
if (el[1].href?.startsWith('http')) {
el[1].target = '_blank'
}
}
)
const output = await renderMarkdown(tree)Content Migration
Convert between attribute styles or normalize formatting:
import { parse } from 'comark'
import { renderMarkdown } from 'comark/render'
async function normalizeDocument(source: string) {
const tree = await parse(source)
// Re-serialize with consistent formatting using codeblock style
return renderMarkdown(tree, { maxInlineAttributes: 0 })
}
// Or migrate to frontmatter style
async function migrateToFrontmatter(source: string) {
const tree = await parse(source)
return renderMarkdown(tree, {
maxInlineAttributes: 0,
blockAttributesStyle: 'frontmatter',
})
}Programmatic Document Generation
Build a Comark document from data:
import { renderMarkdown } from 'comark/render'
import type { ComarkTree } from 'comark'
const tree: ComarkTree = {
frontmatter: { title: 'API Reference', date: '2025-01-01' },
meta: {},
nodes: [
['h1', {}, 'API Reference'],
['p', {}, 'Welcome to the API docs.'],
['alert', { type: 'info' }, 'This API is in beta.'],
],
}
const markdown = await renderMarkdown(tree)