---
title: "AST API"
description: "Traverse and modify the MarkdownDocument AST after parsing using the visit() utility."
canonical_url: "https://comark.dev/plugins/custom/ast-api"
---
# AST API

> Traverse and modify the MarkdownDocument AST after parsing using the visit() utility.

The `comark/utils` module exports `visit`, a tree traversal utility for transforming AST nodes inside a [`post` hook](https://comark.dev/plugins/custom/plugin-api#lifecycle).

## `visit(tree, checker, visitor)`{lang="ts"}

Traverses all nodes in a `MarkdownDocument`, calling `visitor` on every node that passes `checker`.

**Parameters:**

- `tree` - The [`MarkdownDocument`](https://comark.dev/getting-started/document-model#document-structure) to traverse
- `checker` - A predicate `(node: Node) => boolean`, return `true` to visit a node
- `visitor` - A transform `(node: Node) => Node | false | void`, see [`Node`](https://comark.dev/getting-started/document-model#node-model)

**Returns:** `void` (mutations are applied in place)

The visitor return value controls what happens to the node:

| Return value | Effect                                   |
| ------------ | ---------------------------------------- |
| `void`       | Leave the node unchanged                 |
| `Node`       | Replace the node with the returned value |
| `false`      | Remove the node from the tree            |

**Example:**

```typescript
import { defineComarkPlugin } from 'comark/parse'
import { visit } from 'comark/utils'

export default defineComarkPlugin(() => ({
  name: 'word-count',
  post(state) {
    let count = 0
    visit(state.tree,
      (node) => typeof node === 'string',
      (node) => { count += (node as string).split(/\s+/).filter(Boolean).length }
    )
    state.tree.meta.wordCount = count
  },
}))
```

---

## Use cases

### Replacing nodes

Return a new node to replace the matched one. The following example wraps bare URL text nodes in anchor elements:

```typescript
import type { Node } from 'comark'
import { defineComarkPlugin } from 'comark/parse'
import { visit } from 'comark/utils'

export default defineComarkPlugin(() => ({
  name: 'auto-link',
  post(state) {
    const urlPattern = /https?:\/\/[^\s]+/g

    visit(state.tree,
      (node) => typeof node === 'string',
      (node) => {
        const text = node as string
        if (!urlPattern.test(text)) return
        return ['a', { href: text }, text] as Node
      }
    )
  },
}))
```

### Mutating attributes

Return `void` and mutate the node in place when you only need to update attributes:

```typescript
import { defineComarkPlugin } from 'comark/parse'
import { visit } from 'comark/utils'

export default defineComarkPlugin(() => ({
  name: 'styled-tables',
  post(state) {
    visit(state.tree,
      (node) => Array.isArray(node) && node[0] === 'table',
      (node) => {
        const el = node as [string, Record<string, any>, ...any[]]
        el[1].class = [el[1].class, 'styled-table'].filter(Boolean).join(' ')
      }
    )
  },
}))
```

### Removing nodes

Return `false` to remove matched nodes entirely:

```typescript
import { defineComarkPlugin } from 'comark/parse'
import { visit } from 'comark/utils'

export default defineComarkPlugin(() => ({
  name: 'strip-html-comments',
  post(state) {
    visit(state.tree,
      (node) => Array.isArray(node) && node[0] === null,
      () => false
    )
  },
}))
```

---

- [Document Model](https://comark.dev/getting-started/document-model)
- [Plugin API](https://comark.dev/plugins/custom/plugin-api)


## Sitemap

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