---
title: "Render API"
description: "Convert a Markdown AST back to markdown, preserving frontmatter and component syntax."
canonical_url: "https://comark.dev/reference/render"
---
# Render API

> Convert a Markdown AST back to markdown, preserving frontmatter and component syntax.

## `renderMarkdown(document, options?)`{lang="ts"}

Converts a `MarkdownDocument` back into a markdown string, preserving frontmatter, component syntax, and attributes.

**Parameters:**

- `document` - The `MarkdownDocument` returned by `parseMarkdown()` or `createMarkdownParser()`
- `options?` - Optional render options

**Returns:** `Promise<string>` (the serialized markdown string)

**Example:**

<code-group>
~~~typescript [render.ts]
import { parseMarkdown } from 'comark'
import { renderMarkdown } from 'comark/render'

const document = await parseMarkdown(`---
title: Hello
---

# Hello **World**

::alert{type="info"}
This is an alert
::
`)

const markdown = await renderMarkdown(document)
~~~


```markdown [Output]
---
title: Hello
---

# Hello **World**

::alert{type="info"}
This is an alert
::
```
</code-group>

<tip>
For the complete parsed document and node types, see the [Document Model](https://comark.dev/getting-started/document-model#node-model).
</tip>

---

## Options

| Option                                                | Type                                                    | Default                        | Description                                                                                                          |
| ----------------------------------------------------- | ------------------------------------------------------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
| [`maxInlineAttributes`](#options-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`](#options-frontmatteroptions)   | `DumpOptions`                                           | `{ indent: 2, lineWidth: -1 }` | js-yaml [DumpOptions](https://github.com/nodeca/js-yaml#dump-object---options-) passed to the frontmatter serializer |
| [`components`](#options-components)                   | `Record<string, NodeHandler \| ConditionalNodeHandler>` | `{}`                           | Custom render handlers for specific elements                                                                         |
| [`data`](#options-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:

<code-group>
```typescript [maxInlineAttributes: 3 (default)]
// Renders as inline when 3 or fewer attributes
// ::card{title="Hello" icon="star" color="blue"}

// Switches to block syntax when more than 3
```


~~~mdc [codeblock style (default)]
::card
```yaml [props]
title: Hello
icon: star
color: blue
size: large
```
::
~~~


```mdc [frontmatter style]
::card
---
title: Hello
icon: star
color: blue
size: large
---
::
```
</code-group>

Switch between styles with the `blockAttributesStyle` option:

```typescript
// Use codeblock style (default)
const md = await renderMarkdown(document, { blockAttributesStyle: 'codeblock' })

// Use frontmatter style
const md = await renderMarkdown(document, { blockAttributesStyle: 'frontmatter' })

// Always use block syntax with frontmatter style
const md = await renderMarkdown(document, {
  maxInlineAttributes: 0,
  blockAttributesStyle: 'frontmatter',
})
```

### `frontmatterOptions`

By default frontmatter strings are never line-wrapped (`lineWidth: -1`). Pass any [js-yaml DumpOptions](https://github.com/nodeca/js-yaml#dump-object---options-) via `frontmatterOptions` to override this or control other serialization behaviour:

```typescript
const md = await renderMarkdown(document, {
  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.

```typescript
import { parseMarkdown } from 'comark'
import { renderMarkdown } from 'comark/render'

const document = await parseMarkdown(source)

const markdown = await renderMarkdown(document, {
  components: {
    alert: async ([, attrs, ...children], { render }) => {
      return `::alert{type="${attrs.type}"}\n${await render(children)}\n::`
    }
  }
})
```

#### Conditional handlers

When a handler should apply based on a node's attributes rather than its tag name, use a `ConditionalNodeHandler`:

```typescript
const markdown = await renderMarkdown(document, {
  components: {
    // The key is arbitrary; matching is done by the `match` function
    infoAlert: {
      match: (node) => node[0] === 'alert' && node[1].type === 'info',
      handler: async ([, , ...children], { render }) => {
        return `> **Info:** ${(await render(children)).trim()}\n`
      },
    },
  },
})
```

Conditional handlers are checked after direct tag-based handlers. The first match wins.

Some plugins export pre-built conditional handlers. For example, the [footnotes plugin](https://comark.dev/plugins/built-in/footnotes#stringify-markdown-rendering) provides `Footnote`:

```typescript
import footnotes, { Footnote } from 'comark/plugins/footnotes'

const document = await parseMarkdown(md, { plugins: [footnotes()] })
const markdown = await renderMarkdown(document, {
  components: { footnotes: Footnote },
})
```

### `data`

Additional data passed to every component handler via the context object. Useful for sharing configuration or state across handlers:

```typescript
const markdown = await renderMarkdown(document, {
  data: { baseUrl: 'https://example.com' },
  components: {
    a: async ([, attrs, ...children], { render, data }) => {
      const href = attrs.href?.startsWith('/')
        ? `${data.baseUrl}${attrs.href}`
        : attrs.href
      return `[${await render(children)}](${href})`
    }
  }
})
```

---

## Use cases

### Round-trip transformation

Parse markdown, programmatically transform the AST, then serialize back:

```typescript
import { parseMarkdown } from 'comark'
import { renderMarkdown } from 'comark/render'
import { visit } from 'comark/utils'

const document = await parseMarkdown(source)

// Add an "external" class to all links
visit(document,
  (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(document)
```

### Content migration

Convert between attribute styles or normalize formatting:

```typescript
import { parseMarkdown } from 'comark'
import { renderMarkdown } from 'comark/render'

async function normalizeDocument(source: string) {
  const document = await parseMarkdown(source)
  // Re-serialize with consistent formatting using codeblock style
  return renderMarkdown(document, { maxInlineAttributes: 0 })
}

// Or migrate to frontmatter style
async function migrateToFrontmatter(source: string) {
  const document = await parseMarkdown(source)
  return renderMarkdown(document, {
    maxInlineAttributes: 0,
    blockAttributesStyle: 'frontmatter',
  })
}
```

### Programmatic document generation

Build a Comark document from data:

```typescript
import { renderMarkdown } from 'comark/render'
import type { MarkdownDocument } from 'comark'

const document: MarkdownDocument = {
  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(document)
```

---

---

- [Document Model](https://comark.dev/getting-started/document-model)
- [Parse API](https://comark.dev/reference/parse)


## Sitemap

See the full [sitemap](https://comark.dev/sitemap.md) for all pages.
