---
title: "Markdown AST Plugins"
description: "Fumadocs processes documentation through an extensible pipeline of remark and rehype plugins that transform abstract syntax trees to support features like admonitions, code tabs, and table of conte..."
last_updated: "2026-10-12T07:53:59.827853+00:00"
canonical_url: "https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/core-architecture/markdown-ast-plugins"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [packages/core/src/mdx-plugins/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/index.ts)
- [packages/obsidian/src/remark/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/obsidian/src/remark/index.ts)
- [packages/core/src/mdx-plugins/remark-llms.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-llms.ts)
- [packages/mdx/src/loaders/mdx/remark-postprocess.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/loaders/mdx/remark-postprocess.ts)
- [packages/core/src/mdx-plugins/remark-structure.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-structure.ts)
- [packages/core/src/content/mdx/preset-bundler.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/content/mdx/preset-bundler.ts)
- [packages/core/src/mdx-plugins/rehype-toc.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/rehype-toc.ts)
- [packages/preview/src/lib/md.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/lib/md.ts)
- [packages/core/src/mdx-plugins/remark-steps.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-steps.ts)
- [packages/doc-gen/src/remark-docgen.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/doc-gen/src/remark-docgen.ts)
- [packages/mdx/src/rolldown/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/rolldown/index.ts)
- [packages/core/src/content/mdx/preset-runtime.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/content/mdx/preset-runtime.ts)
- [packages/obsidian/src/remark/remark-convert.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/obsidian/src/remark/remark-convert.ts)
- [packages/doc-gen/src/remark-install.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/doc-gen/src/remark-install.ts)
- [packages/core/src/mdx-plugins/remark-mdx-files.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-mdx-files.ts)
- [packages/typescript/src/lib/remark-auto-type-table.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/typescript/src/lib/remark-auto-type-table.ts)
- [packages/core/src/mdx-plugins/remark-directive-admonition.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-directive-admonition.ts)
- [packages/obsidian/src/mdx/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/obsidian/src/mdx/index.ts)
- [packages/core/src/mdx-plugins/remark-gfm.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-gfm.ts)
- [packages/core/src/mdx-plugins/remark-npm.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-npm.ts)
- [packages/core/src/mdx-plugins/remark-heading.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-heading.ts)
- [packages/core/src/mdx-plugins/remark-mdx-mermaid.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-mdx-mermaid.ts)
- [packages/core/src/mdx-plugins/remark-code-tab.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-code-tab.ts)
- [packages/mdx/src/config/preset.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/config/preset.ts)
- [packages/local-md/src/md/compiler.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/local-md/src/md/compiler.ts)
- [packages/core/src/mdx-plugins/rehype-code.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/rehype-code.ts)
- [packages/content-collections/src/configuration.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content-collections/src/configuration.ts)
- [packages/core/src/mdx-plugins/remark-admonition.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-admonition.ts)
- [packages/mdx/src/loaders/mdx/remark-include.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/loaders/mdx/remark-include.ts)
- [packages/core/src/content/md.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/content/md.ts)
</details>

## Overview

Fumadocs processes documentation through an extensible pipeline of remark and rehype plugins that transform abstract syntax trees to support features like admonitions, code tabs, and [table of contents](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/ui-layouts/table-of-contents) generation. These plugins integrate directly with [MDX Loader Pipeline](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/mdx-pipeline/mdx-loader-pipeline), [Source Loader](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/core-architecture/source-loader), and [Content Collections Adapter](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/content-sources/content-collections-adapter) to standardize markdown compilation across build tools and runtimes.

Sources: [packages/core/src/mdx-plugins/index.ts:1-16](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/index.ts#L1-L16), [packages/core/src/content/mdx/preset-bundler.ts:23-92](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/content/mdx/preset-bundler.ts#L23-L92), [packages/content-collections/src/configuration.ts:58-124](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content-collections/src/configuration.ts#L58-L124)

## Admonition parsing and directive transformations

### Directive-Based Admonition Containers

The `remarkDirectiveAdmonition` plugin processes `containerDirective` nodes within the [MDX Loader Pipeline](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/mdx-pipeline/mdx-loader-pipeline) to support standard callout syntaxes, particularly for migrations from Docusaurus. It inspects tree nodes using `unist-util-visit`, checking whether a node name matches any entry in the configured `types` mapping. When a match is found, the plugin transforms the AST node into an `mdxJsxFlowElement` representing a callout container.

```typescript
export function remarkDirectiveAdmonition({
  tags: {
    CalloutContainer = 'CalloutContainer',
    CalloutTitle = 'CalloutTitle',
    CalloutDescription = 'CalloutDescription',
  } = {},
  types = {
    note: 'info',
    tip: 'info',
    info: 'info',
    warn: 'warning',
    warning: 'warning',
    danger: 'error',
    success: 'success',
  },
}: RemarkDirectiveAdmonitionOptions = {}): Transformer<Root, Root> {
```
Sources: [packages/core/src/mdx-plugins/remark-directive-admonition.ts:29-44](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-directive-admonition.ts#L29-L44)

The mapping between input directive names and target callout types defaults to specific standard identifiers.

| Directive Name | Target Callout Type |
| -------------- | ------------------- |
| `note` | `info` |
| `tip` | `info` |
| `info` | `info` |
| `warn` | `warning` |
| `warning` | `warning` |
| `danger` | `error` |
| `success` | `success` |

Sources: [packages/core/src/mdx-plugins/remark-directive-admonition.ts:35-43](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-directive-admonition.ts#L35-L43)

During transformation, any directive attributes present on the container are extracted into `mdxJsxAttribute` objects, alongside the primary `type` attribute. Children of the container are partitioned: paragraphs flagged with `directiveLabel` are routed into a title element named via `CalloutTitle`, while all remaining content blocks form the children of a description element named via `CalloutDescription`.

```typescript
const titleNodes: PhrasingContent[] = [];
const descriptionNodes: (BlockContent | DefinitionContent)[] = [];

for (const item of node.children) {
  if (item.type === 'paragraph' && item.data?.directiveLabel) {
    titleNodes.push(...item.children);
  } else {
    descriptionNodes.push(item);
  }
}

const children: MdxJsxFlowElement[] = [];

if (titleNodes.length > 0) {
  children.push({
    type: 'mdxJsxFlowElement',
    name: CalloutTitle,
    attributes: [],
    children: titleNodes as BlockContent[],
  });
}

if (descriptionNodes.length > 0) {
  children.push({
    type: 'mdxJsxFlowElement',
    name: CalloutDescription,
    attributes: [],
    children: descriptionNodes,
  });
}

```
Sources: [packages/core/src/mdx-plugins/remark-directive-admonition.ts:65-95](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-directive-admonition.ts#L65-L95)

> [!NOTE]
> `remarkDirectiveAdmonition` requires `remark-directive` to be configured beforehand in the unified pipeline to parse container directive syntax correctly.
> Sources: [packages/core/src/mdx-plugins/remark-directive-admonition.ts:26-28](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-directive-admonition.ts#L26-L28)

### Legacy Admonition Syntax Rewriting

The legacy `remarkAdmonition` plugin provides backward compatibility for custom admonition tags (defaulting to `:::`) by scanning paragraph nodes, flattening their contents, and searching for matching type keys. While deprecated in favor of directive-based parsing, it maps alternative type names and extracts bracketed title meta strings into JSX attributes for standard `Callout` elements.

```typescript
export function remarkAdmonition(options: RemarkAdmonitionOptions = {}): Transformer<Root, Root> {
  const tag = options.tag ?? ':::';
  // compatible with Docusaurus
  const typeMap = options.typeMap ?? {
    info: 'info',
    warn: 'warn',

    note: 'info',
    tip: 'info',
    warning: 'warn',
    danger: 'error',
  };

```
Sources: [packages/core/src/mdx-plugins/remark-admonition.ts:22-34](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-admonition.ts#L22-L34)

> [!WARNING]
> Using nested admonitions with the legacy `remarkAdmonition` parser sets an `hasIntercept` flag that recursively invokes `replaceNodes` on inner child slices.
> Sources: [packages/core/src/mdx-plugins/remark-admonition.ts:40-41](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-admonition.ts#L40-L41), [packages/core/src/mdx-plugins/remark-admonition.ts:80|80](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-admonition.ts#L80-L80)

## Heading slugs and table generation

### Heading Slug Assignment and TOC Extraction

The `remarkHeading` plugin processes markdown trees using `unist-util-visit` to locate heading nodes and assign unique URL-safe identifiers via `github-slugger`. By default, it supports custom heading IDs through a regular expression that inspects the final text node of a heading for a bracketed slug pattern (`[#slug]`). When a custom ID is matched, the plugin assigns it to `heading.data.hProperties.id` and strips the trailing tag from the node value. If no custom ID is present, it flattens the node content and passes it to the configured slug function.

```typescript
const regex = /\s*\[#(?<slug>[^]+?)]\s*$/;
```
Sources: [packages/core/src/mdx-plugins/remark-heading.ts:16-16](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-heading.ts#L16-L16), [packages/core/src/mdx-plugins/remark-heading.ts:63-95](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-heading.ts#L63-L95)

When `generateToc` is enabled, the plugin constructs an array of `TOCItemType` entries storing the title text, hash URL, and heading depth, attaching the resulting collection directly to `file.data.toc`.

```typescript
if (generateToc) {
  toc.push({
    title: flattened ?? flattenNode(heading),
    url: `#${props.id}`,
    depth: heading.depth,
  });
}
```
Sources: [packages/core/src/mdx-plugins/remark-heading.ts:86-92](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-heading.ts#L86-L92), [packages/core/src/mdx-plugins/remark-heading.ts:97-97](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-heading.ts#L97-L97)

> [!NOTE]
> `remarkHeading` initializes a fresh `Slugger` instance and resets it on each transformer execution to guarantee deterministic ID generation across builds.
> Sources: [packages/core/src/mdx-plugins/remark-heading.ts:53-62](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-heading.ts#L53-L62)

### Structured Data Extraction

The `remarkStructure` plugin parses MDAST trees to build searchable structured data containing headings and content blocks. It scans nodes matching specified types, defaulting to paragraphs, blockquotes, table cells, headings, and leaf MDX flow elements.

```typescript
export const remarkStructureDefaultOptions = {
  types: ['heading', 'paragraph', 'blockquote', 'tableCell', 'mdxJsxFlowElement'],
  mdxTypes(node) {
    return !node.children || node.children.length === 0;
  },
  exportAs: false,
} satisfies StructureOptions;
```
Sources: [packages/core/src/mdx-plugins/remark-structure.ts:94-100](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-structure.ts#L94-L100)

If a heading node lacks an `hProperties.id`, `remarkStructure` emits a warning and skips it, requiring `remarkHeading` to run beforehand in the pipeline.

```typescript
if (typeof id !== 'string') {
  console.warn(
    '[remark-structure] hProperties.id is missing in heading node, it is required to generate heading data. You can add remark-heading prior to remark-structure to generate heading IDs.',
  );
  return 'skip';
}
```
Sources: [packages/core/src/mdx-plugins/remark-structure.ts:173-178](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-structure.ts#L173-L178)

Collected blocks associate their content with the most recently encountered heading ID, storing the structured output in `file.data.structuredData` or serializing it as an MDX export when `exportAs` is configured.

```typescript
file.data.structuredData = data;
if (exportAs) {
  tree.children.unshift(
    toMdxExport(typeof exportAs === 'string' ? exportAs : 'structuredData', data),
  );
}
```
Sources: [packages/core/src/mdx-plugins/remark-structure.ts:204-209](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-structure.ts#L204-L209)

### Rehype Table of Contents Generation

The `rehypeToc` plugin inspects HTML element trees for heading tags (`h1` through `h6`) to build a table of contents directly from HAST nodes. It recognizes control tags appended to heading text to alter behavior: `[!toc]` suppresses the heading from the table of contents and strips the tag, while `[toc]` includes the heading but removes the tag from the rendered output.

```typescript
const TocOnlyTag = '[toc]';
const NoTocTag = '[!toc]';
const HeadingTags = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']);
```
Sources: [packages/core/src/mdx-plugins/rehype-toc.ts:51-53](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/rehype-toc.ts#L51-L53), [packages/core/src/mdx-plugins/rehype-toc.ts:77-91](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/rehype-toc.ts#L77-L91)

| Configuration Property | Type | Default Value | Purpose |
| ---------------------- | ---- | ------------- | ------- |
| `exportToc` | `boolean \| { as: 'data' } \| { as: 'esm', name: string }` | `true` | Determines whether the table of contents is exported as an ESM variable or stored in virtual file data. |

Sources: [packages/core/src/mdx-plugins/rehype-toc.ts:8-30](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/rehype-toc.ts#L8-L30), [packages/core/src/mdx-plugins/rehype-toc.ts:55-61](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/rehype-toc.ts#L55-L61)

When `exportToc` is configured as an ESM export (defaulting to the name `toc`), the plugin converts HAST heading nodes into ESTree JSX fragments using `hast-util-to-estree`.

```typescript
if (exportToc.as === 'esm') {
  const esmItems: {
    title: JSXElement;
    url: string;
    depth: number;
    _step?: number;
  }[] = [];
  for (const item of items) {
    const root = toEstree(item.title, {
      elementAttributeNameCase: 'react',
      stylePropertyNameCase: 'dom',
    }).body[0];

    if (root.type === 'ExpressionStatement' && root.expression.type === 'JSXElement') {
      esmItems.push({
        ...item,
        title: root.expression,
      });
    }
```
Sources: [packages/core/src/mdx-plugins/rehype-toc.ts:110-128](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/rehype-toc.ts#L110-L128)

> [!WARNING]
> Headings without a valid string identifier in their properties are skipped by `rehypeToc` and omitted from the generated table of contents.
> Sources: [packages/core/src/mdx-plugins/rehype-toc.ts:73-74](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/rehype-toc.ts#L73-L74)

## Sequential step list conversion

### Step Detection and Parsing

The `remarkSteps` plugin transforms consecutive step headings and their accompanying content into structured MDX JSX flow elements. It accepts an options object to customize CSS class names for both the outer steps container and individual step items.

| Configuration Property | Type | Default Value | Purpose |
| ---------------------- | ---- | ------------- | ------- |
| `steps` | `string` | `'fd-steps'` | Class name assigned to the outer wrapper container div element. |
| `step` | `string` | `'fd-step'` | Class name assigned to each individual step item container div element. |

Sources: [packages/core/src/mdx-plugins/remark-steps.ts:7-21](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-steps.ts#L7-L21), [packages/core/src/mdx-plugins/remark-steps.ts:29-32](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-steps.ts#L29-L32)

To identify step headings, the plugin evaluates heading text against a regular expression pattern matching numeric prefixes like `1. Hello World`, or checks for the trailing tag `[step]` via `handleTag`. When a valid step pattern matches, the plugin strips the marker from the text node and injects a `data-fd-step` property containing the incremental step index onto the heading's data properties.

```typescript
const StepRegex = /^(\d+)\.\s(.+)$/;
const StepTag = '[step]';
```
Sources: [packages/core/src/mdx-plugins/remark-steps.ts:23-24](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-steps.ts#L23-L24), [packages/core/src/mdx-plugins/remark-steps.ts:70-90](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-steps.ts#L70-L90), [packages/core/src/mdx-plugins/remark-steps.ts:124-132](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-steps.ts#L124-L132)

> [!WARNING]
> If a heading has a mismatched depth or fails step parsing while a sequence is actively collecting, the plugin immediately terminates the current step group via `onEnd()`.
> Sources: [packages/core/src/mdx-plugins/remark-steps.ts:118-127](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-steps.ts#L118-L127)

## Code tabs and command generation

### Package Manager Conversion and Tabs Generation

Fumadocs provides remark plugins to rewrite code blocks into multi-tab package manager interfaces. The core pipeline includes `remarkNpm` and `remarkCodeTab`, alongside legacy support from `remarkInstall`. 

| Plugin Name | Target Language / Marker | Output Element |
| ----------- | ------------------------ | -------------- |
| `remarkNpm` | `package-install`, `npm` | CodeBlockTabs / custom triggers |
| `remarkInstall` | `package-install` | Tabs, Tab |
| `remarkCodeTab` | Code blocks with `tab` attribute | CodeBlockTabs or Tabs / Tab |

Sources: [packages/core/src/mdx-plugins/remark-npm.ts:60-73](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-npm.ts#L60-L73), [packages/doc-gen/src/remark-install.ts:60](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/doc-gen/src/remark-install.ts#L60), [packages/core/src/mdx-plugins/remark-code-tab.ts:255-259](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-code-tab.ts#L255-L259)

The `remarkNpm` plugin visits code blocks in the Markdown AST and detects nodes where the language is `package-install` or `npm`. For `package-install` blocks, if the text does not start with `npm` or `npx`, it automatically prepends `npm install`. It then maps over configured package managers to translate commands and generate code block tabs.

```typescript
export function remarkNpm({
  persist = false,
  packageManagers = [
    { command: (cmd) => cmd, name: 'npm' },
    { command: (cmd) => convertLines(cmd, 'pnpm'), name: 'pnpm' },
    { command: (cmd) => convertLines(cmd, 'yarn'), name: 'yarn' },
    { command: (cmd) => convertLines(cmd, 'bun'), name: 'bun' },
  ],
}: RemarkNpmOptions = {}): Transformer<Root, Root> {
```
Sources: [packages/core/src/mdx-plugins/remark-npm.ts:46-54](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-npm.ts#L46-L54)

> [!NOTE]
> `remarkInstall` from `@fumadocs/doc-gen` is deprecated in favor of `remarkNpm` from `fumadocs-core/mdx-plugins`, which serves as a drop-in replacement.
> Sources: [packages/doc-gen/src/remark-install.ts:45](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/doc-gen/src/remark-install.ts#L45)

### Custom Code Tabs and Attribute Parsing

The `remarkCodeTab` plugin processes adjacent code blocks decorated with meta attributes like `tab` and `tab-group`. It supports two layout handlers defined in `Types`: `CodeBlockTabs` and `Tabs`.

| Option Property | Type | Default Value | Purpose |
| --------------- | ---- | ------------- | ------- |
| `Tabs` | `'CodeBlockTabs' \| 'Tabs'` | `'CodeBlockTabs'` | Determines the component name used for rendering grouped tab containers. |
| `parseMdx` | `boolean` | `false` | Enables parsing of embedded MDX inside tab trigger values. |

Sources: [packages/core/src/mdx-plugins/remark-code-tab.ts:11-21](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-code-tab.ts#L11-L21), [packages/core/src/mdx-plugins/remark-code-tab.ts:206](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-code-tab.ts#L206)

During visitation, `remarkCodeTab` uses `parseCodeBlockAttributes` to extract `tab` and `tab-group` metadata from code block strings, assigns them to node data properties, and strips them from the raw meta string so syntax highlighters do not receive unknown flags.

```typescript
for (; end < node.children.length; end++) {
  const child = node.children[end];
  if (child.type !== 'code' || !child.meta) {
    close();
    continue;
  }

  const meta = parseCodeBlockAttributes(child.meta, ['tab', 'tab-group']);
  if (!meta.attributes.tab) {
    close();
    continue;
  }

  if (start === -1) start = end;
  child.meta = meta.rest;
  child.data ??= {};
  child.data.tab = meta.attributes.tab;
  if (meta.attributes['tab-group']) {
    child.data.tabGroup = meta.attributes['tab-group'];
  }
}
```
Sources: [packages/core/src/mdx-plugins/remark-code-tab.ts:248-268](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-code-tab.ts#L248-L268)

> [!WARNING]
> When processing `Tabs` in simple mode with `parseMdx` enabled, the plugin checks whether the existing node attributes contain an `items` property before attempting to parse tab names as MDX.
> Sources: [packages/core/src/mdx-plugins/remark-code-tab.ts:220-224](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-code-tab.ts#L220-L224)

## Mermaid diagram code block conversion

### Remark Transformer and AST Replacement

The `remarkMdxMermaid` plugin transforms fenced code blocks containing diagram specifications into dynamic MDX JSX flow elements. It exports an options interface that accepts a custom language identifier.

| Option Property | Type | Default Value | Purpose |
| --------------- | ---- | ------------- | ------- |
| `lang` | `string` | `'mermaid'` | Specifies the target code block language identifier to match during AST visitation. |

Sources: [packages/core/src/mdx-plugins/remark-mdx-mermaid.ts:21-26](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-mdx-mermaid.ts#L21-L26)

The plugin returns a unified transformer function that traverses the markdown abstract syntax tree using `unist-util-visit`. It targets nodes of type `code`, validates their positional indices and parent references, and compares their language property against the configured target language.

```typescript
export function remarkMdxMermaid(options: RemarkMdxMermaidOptions = {}): Transformer<Root, Root> {
  const { lang = 'mermaid' } = options;

  return (tree) => {
    visit(tree, 'code', (node, idx, parent) => {
      if (node.lang !== lang || !node.value || typeof idx !== 'number' || !parent) return;

      parent.children[idx] = toMDX(node.value);
    });
  };
```
Sources: [packages/core/src/mdx-plugins/remark-mdx-mermaid.ts:31-40](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-mdx-mermaid.ts#L31-L40)

> [!CAUTION]
> The visitor function skips code block nodes where `idx` is not a number or where `parent` is undefined, preventing out-of-bounds mutations on root or unattached AST nodes.
> Sources: [packages/core/src/mdx-plugins/remark-mdx-mermaid.ts:36](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-mdx-mermaid.ts#L36)

### MDX JSX Node Generation

When a matching code block is identified, the plugin delegates conversion to the `toMDX` helper function. This function constructs an `MdxJsxFlowElement` node configured to render the `<Mermaid />` component.

```typescript
function toMDX(code: string): MdxJsxFlowElement {
  return {
    type: 'mdxJsxFlowElement',
    name: 'Mermaid',
    attributes: [
      {
        type: 'mdxJsxAttribute',
        name: 'chart',
        value: code.trim(),
      },
    ],
    children: [],
  };
}
```
Sources: [packages/core/src/mdx-plugins/remark-mdx-mermaid.ts:6-19](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-mdx-mermaid.ts#L6-L19)

> [!NOTE]
> The source code inside the matched code block is trimmed via `code.trim()` before being assigned to the `chart` attribute of the generated JSX element, removing leading and trailing whitespace from the diagram definition.
> Sources: [packages/core/src/mdx-plugins/remark-mdx-mermaid.ts:14](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-mdx-mermaid.ts#L14)

## LLM exports and postprocessing pipeline

### Remark LLM Stringification and Placeholder Transformation

The `remarkLLMs` plugin processes markdown trees to generate clean plain-text representations and exportable string constants suitable for large language model ingestion. It accepts an options interface extending stringifier configurations with export name, heading identifier inclusion, element filtering, and placeholder conversion settings.

| Option Property | Type | Default Value | Purpose |
| --------------- | ---- | ------------- | ------- |
| `as` | `string` | `'_markdown'` | Defines the variable name for the exported markdown string. |
| `headingIds` | `boolean` | `true` | Appends explicit heading anchors (`[#id]`) to output headings when present. |
| `filterElement` | `StringifyOptions['filterElement']` | Custom function | Filters elements; excludes `mdxjsEsm` by default while retaining others. |
| `mdxAsPlaceholder` | `string[]` | `undefined` | Target MDX component tag names to stringify as runtime placeholders. |
| `_data` | `boolean` | `false` | Stores the generated markdown string inside `file.data.markdown`. |

Sources: [packages/core/src/mdx-plugins/remark-llms.ts:9-55](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-llms.ts#L9-L55)

The internal stringifier customizes node serialization by invoking `placeholder` for matching `mdxJsxFlowElement` and `mdxJsxTextElement` nodes listed in `mdxAsPlaceholder`.

```typescript
stringify(node, parent, state, info, ctx) {
  if (mdxAsPlaceholder) {
    switch (node.type) {
      case 'mdxJsxFlowElement':
      case 'mdxJsxTextElement':
        if (node.name && mdxAsPlaceholder.includes(node.name))
          return placeholder(node, parent, state, info);
    }
  }

  return rest.stringify?.(node, parent, state, info, ctx);
},
```
Sources: [packages/core/src/mdx-plugins/remark-llms.ts:80-91](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-llms.ts#L80-L91)

> [!NOTE]
> The `placeholder` helper extracts valid non-expression attributes, formats element phrasing children, and serializes the resulting payload into a null-delimited JSON string format conforming to `PlaceholderData`.
> Sources: [packages/core/src/mdx-plugins/remark-llms.ts:112-129](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/remark-llms.ts#L112-L129)

### Postprocessing Pipeline and Frontmatter Extraction

The `remarkPostprocess` transformer orchestrates document metadata generation, reference extraction, and optional LLM string export. It inspects configuration options to determine which values to extract and export into the module AST.

| Option Property | Type | Default Value | Purpose |
| --------------- | ---- | ------------- | ------- |
| `_format` | `'md' \| 'mdx'` | Required | Specifies the underlying document format. |
| `valueToExport` | `string[]` | `[]` | Additional property keys from `file.data` to export as named variables. |
| `includeProcessedMarkdown` | `boolean \| LLMsOptions` | `false` | Enables `remarkLLMs` processing and exports the resulting string. |
| `extractLinkReferences` | `boolean` | `false` | Collects link href values and exports them via `extractedReferences`. |
| `includeMDAST` | `boolean \| { removePosition?: boolean }` | `false` | Serializes and exports the document MDAST via `_mdast`. |

Sources: [packages/mdx/src/loaders/mdx/remark-postprocess.ts:14-40](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/loaders/mdx/remark-postprocess.ts#L14-L40)

> [!WARNING]
> If `frontmatter.title` is missing, the postprocessor traverses the AST to find the first level-1 heading and flattens its text nodes to auto-populate the document title.
> Sources: [packages/mdx/src/loaders/mdx/remark-postprocess.ts:56-64](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/loaders/mdx/remark-postprocess.ts#L56-L64)

Exported declarations are injected into the AST by converting JavaScript values into estree nodes using `valueToEstree`, wrapped in an `mdxjsEsm` node via `getMdastExport`, and prepended to the tree's children array.

```typescript
function getMdastExport(name: string, value: unknown): RootContent {
  return {
    type: 'mdxjsEsm',
    value: '',
    data: {
      estree: {
        type: 'Program',
        sourceType: 'module',
        body: [
          {
            type: 'ExportNamedDeclaration',
            attributes: [],
            specifiers: [],
            source: null,
            declaration: {
              type: 'VariableDeclaration',
              kind: 'let',
              declarations: [
                {
                  type: 'VariableDeclarator',
                  id: {
                    type: 'Identifier',
                    name,
                  },
                  init: valueToEstree(value),
                },
              ],
            },
          },
        ],
      },
    },
  };
}
```
Sources: [packages/mdx/src/loaders/mdx/remark-postprocess.ts:128-161](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/loaders/mdx/remark-postprocess.ts#L128-L161)

## Plugin presets and runtime aggregation

### Pipeline Composition and Resolution

Fumadocs provides unified configuration presets that compose default remark and rehype plugin pipelines across different build environments and runtimes. The core package exports bundler and runtime preset factories (`mdxPreset`) alongside an MDX configuration preset (`applyMdxPreset`) that handles environments such as bundler or runtime.

| Preset Function | Package | Environment Context | Default Output Format |
| --------------- | ------- | ------------------- | --------------------- |
| `mdxPreset` | `fumadocs-core/content/mdx/preset-bundler` | Bundler build tools (Next.js, Vite, Webpack) | Unmodified / default |
| `mdxPreset` | `fumadocs-core/content/mdx/preset-runtime` | Local or runtime execution | `function-body` |
| `applyMdxPreset` | `fumadocs/config/preset` | Configurable (`bundler` or `runtime`) | `function-body` (for runtime) |

Sources: [packages/core/src/content/mdx/preset-bundler.ts:26-92](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/content/mdx/preset-bundler.ts#L26-L92), [packages/core/src/content/mdx/preset-runtime.ts:23-83](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/content/mdx/preset-runtime.ts#L23-L83), [packages/mdx/src/config/preset.ts:50-131](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/config/preset.ts#L50-L131)

The plugin resolution layer accepts user-defined arrays or functions, conditionally appending default remark plugins like `remarkGfm`, `remarkHeading`, `remarkImage`, `remarkCodeTab`, `remarkNpm`, and `remarkStructure`, as well as rehype plugins like `rehypeCode` and `rehypeToc`.

```typescript
const remarkPlugins = await resolvePlugins(
  (v) => [
    import('remark-gfm').then((mod) => mod.default),
    remarkHeadingOptions !== false &&
      import('@/mdx-plugins/remark-heading').then((mod) => [
        mod.remarkHeading,
        {
          generateToc: false,
          ...remarkHeadingOptions,
        },
      ]),
    _include ?? false,
    remarkImageOptions !== false &&
      import('@/mdx-plugins/remark-image').then((mod) => [
        mod.remarkImage,
        {
          ...remarkImageOptions,
          useImport: remarkImageOptions?.useImport ?? true,
        },
      ]),
    remarkCodeTabOptions !== false &&
      import('@/mdx-plugins/remark-code-tab').then((mod) => [
        mod.remarkCodeTab,
        remarkCodeTabOptions,
      ]),
    remarkNpmOptions !== false &&
      import('@/mdx-plugins/remark-npm').then((mod) => [mod.remarkNpm, remarkNpmOptions]),
    ...v,
    remarkStructureOptions !== false &&
      import('@/mdx-plugins/remark-structure').then((mod) => [
        mod.remarkStructure,
        {
          exportAs: 'structuredData',
          ...remarkStructureOptions,
        } satisfies Plugins.StructureOptions,
      ]),
  ],
  mdxOptions.remarkPlugins,
);

const rehypePlugins = await resolvePlugins(
  (v) => [
    rehypeCodeOptions !== false &&
      import('@/mdx-plugins/rehype-code').then((mod) => [mod.rehypeCode, rehypeCodeOptions]),
    ...v,
    import('@/mdx-plugins/rehype-toc').then((mod) => mod.rehypeToc),
  ],
  mdxOptions.rehypePlugins,
);
```
Sources: [packages/core/src/content/mdx/preset-bundler.ts:38-86](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/content/mdx/preset-bundler.ts#L38-L86)

> [!TIP]
> Setting any preset option to `false` (such as `remarkImageOptions: false` or `rehypeCodeOptions: false`) explicitly disables that specific plugin from the unified pipeline.
> Sources: [packages/core/src/content/mdx/preset-bundler.ts:15-20](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/content/mdx/preset-bundler.ts#L15-L20), [packages/core/src/content/mdx/preset-bundler.ts:41-73](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/content/mdx/preset-bundler.ts#L41-L73)

### Alternative Runtimes and Vault Compilers

For lightweight markdown rendering and preview components, `createMarkdownCompiler` and `createMarkdownRenderer` instantiate standalone unified processors using `remark`, `remarkRehype`, and user-supplied plugin lists without full MDX compilation baggage.

```typescript
const processor = remark()
  .use(remarkPlugins)
  .use(remarkRehype, remarkRehypeOptions)
  .use(rehypePlugins);
```
Sources: [packages/preview/src/lib/md.ts:37-40](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/lib/md.ts#L37-L40), [packages/core/src/content/md.ts:35-38](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/content/md.ts#L35-L38)

Similarly, the Obsidian integration builds a localized unified processor combining vault-specific remark plugins (`remarkWikilinks`, `remarkConvert`, `remarkObsidianComment`, and `remarkBlockId`) to transform vault files into compatible AST structures.

```typescript
export function getRemarkPlugins(storage: VaultStorage, resolver: VaultResolver): PluggableList {
  return [
    [remarkWikilinks, { resolver, storage }],
    [remarkConvert, { resolver, storage }],
    remarkObsidianComment,
    remarkBlockId,
  ];
```
Sources: [packages/obsidian/src/remark/index.ts:9-15](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/obsidian/src/remark/index.ts#L9-L15), [packages/obsidian/src/mdx/index.ts:24-24](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/obsidian/src/mdx/index.ts#L24-L24)

## Related

- [Syntax Highlighting](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/core-architecture/syntax-highlighting)
- [MDX Loader Pipeline](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/mdx-pipeline/mdx-loader-pipeline)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/llms.txt) for all pages in this wiki.
