---
title: "Bundler Plugins"
description: "Fumadocs provides a set of bundler plugins and low-level adapters that integrate content compilation pipelines into modern build tools and JavaScript runtimes packages/mdx/src/loaders/adapter.ts:1-..."
last_updated: "2026-10-12T07:53:59.700425+00:00"
canonical_url: "https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/mdx-pipeline/bundler-plugins"
---

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

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

- [packages/mdx/src/loaders/adapter.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/loaders/adapter.ts)
- [packages/mdx/src/rolldown/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/rolldown/index.ts)
- [packages/mdx/src/bun/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/bun/index.ts)
- [packages/mdx/src/vite/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/vite/index.ts)
- [packages/asyncapi/src/server/index.tsx](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/asyncapi/src/server/index.tsx)
- [packages/mdx/src/loaders/meta.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/loaders/meta.ts)
- [packages/mdx/src/next/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/next/index.ts)
- [packages/mdx/src/webpack/meta.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/webpack/meta.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/node/_loader.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/node/_loader.ts)
- [packages/mdx/src/loaders/mdx/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/loaders/mdx/index.ts)
- [packages/mdx/src/webpack/mdx.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/webpack/mdx.ts)
- [packages/mdx/src/plugins/index-file.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/plugins/index-file.ts)
- [packages/local-md/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/local-md/src/index.ts)
- [packages/mdx/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/tsdown.config.ts)
- [packages/mdx/package.json](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/package.json)
- [packages/core/src/mdx-plugins/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/index.ts)
- [packages/story/src/webpack/story.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/story/src/webpack/story.ts)
- [packages/mdx/src/runtime/server.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/runtime/server.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/mdx/src/node/loader.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/node/loader.ts)
- [packages/story/src/vite/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/story/src/vite/index.ts)
- [packages/doc-gen/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/doc-gen/tsdown.config.ts)
- [packages/mdx/src/bin.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/bin.ts)
- [packages/api-docs/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/api-docs/tsdown.config.ts)
- [packages/mdx/src/core.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/core.ts)
- [packages/core/src/source/loader.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/source/loader.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/content-collections/src/configuration.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content-collections/src/configuration.ts)
- [packages/asyncapi/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/asyncapi/tsdown.config.ts)
</details>

## Overview

Fumadocs provides a set of bundler plugins and low-level adapters that integrate content compilation pipelines into modern build tools and JavaScript runtimes [packages/mdx/src/loaders/adapter.ts:1-223](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/loaders/adapter.ts), [packages/mdx/src/core.ts:1-340](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/core.ts). These integrations handle document parsing, frontmatter validation, configuration reloading, and virtual entry point generation across Next.js, Vite, Rolldown, Webpack, Node.js, and Bun [packages/mdx/src/next/index.ts:1-207](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/next/index.ts), [packages/mdx/src/vite/index.ts:1-160](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/vite/index.ts), [packages/mdx/src/rolldown/index.ts:1-67](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/rolldown/index.ts), [packages/mdx/src/bun/index.ts:1-44](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/bun/index.ts). By abstracting environment-specific module loading and transformation hooks behind a unified core, the packages maintain consistent document behavior during development and production builds [packages/mdx/src/loaders/adapter.ts:35-59](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/loaders/adapter.ts), [packages/mdx/src/core.ts:148-340](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/core.ts).

Sources: [packages/mdx/src/loaders/adapter.ts:1-223](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/loaders/adapter.ts), [packages/mdx/src/rolldown/index.ts:1-67](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/rolldown/index.ts), [packages/mdx/src/bun/index.ts:1-44](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/bun/index.ts), [packages/mdx/src/vite/index.ts:1-160](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/vite/index.ts), [packages/mdx/src/next/index.ts:1-207](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/next/index.ts), [packages/mdx/src/core.ts:1-340](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/core.ts)

## Next.js Integration and Core Lifecycle

### Initialization and Execution Flow

The Next.js integration initializes through `createMDX`, which sets up core compilation options and checks the `_FUMADOCS_MDX` environment variable to prevent duplicate initialization passes. When invoked, `createMDX` executes `createNextCore` using options normalized by `applyDefaults`. The call-chain sequence `createMDX` → `init` → `devServer` → `initOrReload` proceeds by instantiating the core via `createMDX`, handing control to `init` to evaluate development flags, invoking `devServer` when `isDev` is true to watch configuration files and collections, and finally triggering `initOrReload` to load the configuration and emit compiled outputs.

```mermaid
sequenceDiagram
    participant Index as packages/mdx/src/next/index.ts
    Index->>Index: createMDX()
    Index->>Index: init()
    Index->>Index: devServer()
    Index->>Index: initOrReload()
```

Sources: [packages/mdx/src/next/index.ts:30-38](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/next/index.ts)

1. `createMDX`: Accepts `CreateMDXOptions`, establishes default environment variables, and delegates core creation.
Sources: [packages/mdx/src/next/index.ts:30-38](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/next/index.ts)
2. `init`: Manages asynchronous bootstrap and checks the `dev` boolean flag to determine whether to spin up file system watchers.
Sources: [packages/mdx/src/next/index.ts:124-182](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/next/index.ts)
3. `devServer`: Instantiates a `chokidar` `FSWatcher` instance ignoring the output directory, tracks configuration files and collection directories, and handles hot-reloading on configuration changes.
Sources: [packages/mdx/src/next/index.ts:132-176](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/next/index.ts)
4. `initOrReload`: Loads configuration files via `loadConfig(core, true)` and invokes `core.emit({ write: true })`.
Sources: [packages/mdx/src/next/index.ts:125-130](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/next/index.ts)

> [!WARNING]
> If `process.env._FUMADOCS_MDX` is already set to `'1'`, subsequent calls to `init` are bypassed during module evaluation to prevent concurrent watcher duplication across Next.js worker threads.
Sources: [packages/mdx/src/next/index.ts:34-38](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/next/index.ts)

### Configuration Options and Default Values

The integration relies on `CreateMDXOptions` to configure source paths, output targets, and index generation plugins. These options are processed by `applyDefaults` to guarantee defined parameters.

| Option Key | Type | Default Value | Description |
| :--- | :--- | :--- | :--- |
| `configPath` | `string` | `_Defaults.configPath` | Path to source configuration file. |
| `outDir` | `string` | `_Defaults.outDir` | Directory for output files. |
| `index` | `IndexFilePluginOptions \| false` | `{}` | Index file plugin options or `false` to disable. |

Sources: [packages/mdx/src/next/index.ts:12-26](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/next/index.ts), [packages/mdx/src/next/index.ts:192-198](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/next/index.ts)

> [!NOTE]
> `createNextCore` constructs the underlying core instance with environment set to `'next'` and registers the index plugin if enabled.
Sources: [packages/mdx/src/next/index.ts:200-207](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/next/index.ts)

## MDX Compiler Preset Configuration

### Preset Options and Remark/Rehype Registration

The `mdxPreset` function asynchronous builder resolves and registers remark and rehype plugins into standard MDX processor options. It takes `MDXBundlerPresetOptions` which omits standard remark and rehype options in favor of resolver structures and specific plugin toggles. 

```typescript
export type MDXBundlerPresetOptions = Omit<
  NonNullable<ProcessorOptions>,
  'rehypePlugins' | 'remarkPlugins'
> & {
  rehypePlugins?: ResolvePlugins;
  remarkPlugins?: ResolvePlugins;

  /** @private for `remark-include` */
  _include?: Pluggable;
  remarkStructureOptions?: Plugins.StructureOptions | false;
  remarkHeadingOptions?: Plugins.RemarkHeadingOptions | false;
  remarkImageOptions?: Plugins.RemarkImageOptions | false;
  remarkCodeTabOptions?: Plugins.RemarkCodeTabOptions | false;
  remarkNpmOptions?: Plugins.RemarkNpmOptions | false;
  rehypeCodeOptions?: Plugins.RehypeCodeOptions | false;
};
```
Sources: [packages/core/src/content/mdx/preset-bundler.ts:6-21](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/content/mdx/preset-bundler.ts#L6-L21)

```typescript
export async function mdxPreset(options: MDXBundlerPresetOptions = {}): Promise<ProcessorOptions> {
  const {
    rehypeCodeOptions,
    remarkImageOptions,
    remarkHeadingOptions,
    remarkStructureOptions,
    remarkCodeTabOptions,
    remarkNpmOptions,
    _include,
    ...mdxOptions
  } = options;
```
Sources: [packages/core/src/content/mdx/preset-bundler.ts:26-36](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/content/mdx/preset-bundler.ts#L26-L36)

> [!NOTE]
> Each remark and rehype plugin can be explicitly disabled by passing `false` to its corresponding option key, or customized by passing an object containing plugin-specific parameters.
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)

### Plugin Integration Pipeline

The preset registration pipeline resolves plugins asynchronously through `resolvePlugins`, combining standard defaults with custom user configurations. The pipeline processes remark plugins starting with GitHub Flavored Markdown and conditional expansions.

```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,
);
```
Sources: [packages/core/src/content/mdx/preset-bundler.ts:38-76](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/content/mdx/preset-bundler.ts#L38-L76)

```typescript
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:78-86](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/content/mdx/preset-bundler.ts#L78-L86)

> [!WARNING]
> Disabling `remarkHeadingOptions`, `remarkImageOptions`, `remarkCodeTabOptions`, `remarkNpmOptions`, `remarkStructureOptions`, or `rehypeCodeOptions` by setting them to `false` prevents their dynamic imports from executing during the preset build.
Sources: [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), [packages/core/src/content/mdx/preset-bundler.ts:80-81](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/content/mdx/preset-bundler.ts#L80-L81)

### Preset Configuration Options Reference

| Option Key | Type | Default Behavior / Mapping | Description |
| :--- | :--- | :--- | :--- |
| `remarkPlugins` | `ResolvePlugins` | Combined via `resolvePlugins` | Custom remark plugins appended to the preset. |
| `rehypePlugins` | `ResolvePlugins` | Combined via `resolvePlugins` | Custom rehype plugins appended to the preset. |
| `_include` | `Pluggable` | `undefined` | Private plugin hook for `remark-include`. |
| `remarkStructureOptions` | `Plugins.StructureOptions \| false` | `{ exportAs: 'structuredData' }` | Options for the structure extraction remark plugin. |
| `remarkHeadingOptions` | `Plugins.RemarkHeadingOptions \| false` | `{ generateToc: false }` | Options for heading extraction and [table of contents](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/ui-layouts/table-of-contents) generation. |
| `remarkImageOptions` | `Plugins.RemarkImageOptions \| false` | `{ useImport: true }` | Options for remark image optimization and imports. |
| `remarkCodeTabOptions` | `Plugins.RemarkCodeTabOptions \| false` | `undefined` | Options for code tab grouping transformations. |
| `remarkNpmOptions` | `Plugins.RemarkNpmOptions \| false` | `undefined` | Options for package manager command translation. |
| `rehypeCodeOptions` | `Plugins.RehypeCodeOptions \| false` | `undefined` | Options for [syntax highlighting](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/core-architecture/syntax-highlighting) and code block processing. |

Sources: [packages/core/src/content/mdx/preset-bundler.ts:6-21](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/content/mdx/preset-bundler.ts#L6-L21), [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)

## Index File Codegen Coordination

### Execution Pipeline and Sequence Architecture

The coordination of index file generation begins at the core layer where configuration options are evaluated. The explicit call chain starts with `createMDX`, which delegates through `createNextCore`, initializes the `indexFile` plugin, and executes `generateConfigs` to assemble server options and type definitions.

```typescript
export function createMDX(createOptions: CreateMDXOptions = {}) {
  const core = createNextCore(applyDefaults(createOptions));
```
Sources: [packages/mdx/src/next/index.ts:30-31](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/next/index.ts#L30-L31)

```typescript
function createNextCore(options: Required<CreateMDXOptions>): Core {
  return createCore({
    environment: 'next',
    outDir: options.outDir,
    configPath: options.configPath,
    plugins: [options.index && indexFile(options.index)],
  });
```
Sources: [packages/mdx/src/next/index.ts:200-206](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/next/index.ts#L200-L206)

```typescript
export default function indexFile(options: IndexFilePluginOptions = {}): Plugin {
  const { target = 'default', addJsExtension, browser = true, dynamic = true } = options;
  let dynamicCollections: CollectionItem[];

  function isDynamic(collection: CollectionItem) {
    return (
      (collection.type === 'docs' && collection.docs.dynamic) ||
      (collection.type === 'doc' && collection.dynamic)
    );
  }

  function generateConfigs(core: Core): {
    serverOptions: ServerOptions;
    tc: string;
  } {
    const serverOptions: ServerOptions = {};
    const typeConfigs: string[] = ['import("fumadocs-mdx/runtime/types").InternalTypeConfig'];
    const ctx = core.getPluginContext();

    for (const plugin of core.getPlugins()) {
      const indexFilePlugin = plugin['index-file'];
      if (!indexFilePlugin) continue;

      indexFilePlugin.serverOptions?.call(ctx, serverOptions);
      const config = indexFilePlugin.generateTypeConfig?.call(ctx);
      if (config) typeConfigs.push(config);
    }

    return {
      serverOptions,
      tc: typeConfigs.join(' & '),
    };
  }
```
Sources: [packages/mdx/src/plugins/index-file.ts:51-83](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/plugins/index-file.ts#L51-L83)

```mermaid
sequenceDiagram
    participant IndexNext as packages/mdx/src/next/index.ts
    participant IndexPlugin as packages/mdx/src/plugins/index-file.ts
    IndexNext->>IndexNext: createMDX()
    IndexNext->>IndexNext: createNextCore()
    IndexNext->>IndexPlugin: indexFile()
    IndexPlugin->>IndexPlugin: generateConfigs()
```

Sources: [packages/mdx/src/next/index.ts:30-31](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/next/index.ts#L30-L31), [packages/mdx/src/next/index.ts:200-206](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/next/index.ts#L200-L206), [packages/mdx/src/plugins/index-file.ts:51-83](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/plugins/index-file.ts#L51-L83)

> [!NOTE]
> The function `generateConfigs` iterates over registered core plugins to extract custom `serverOptions` and append any module type configurations (`generateTypeConfig`) into a combined intersection type string.
Sources: [packages/mdx/src/plugins/index-file.ts:62-83](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/plugins/index-file.ts#L62-L83)

### Plugin Options and Configuration Reference

The plugin behavior is governed by `IndexFilePluginOptions`, which controls compilation targets, extension resolution, and output file entry generation.

```typescript
export interface IndexFilePluginOptions {
  target?: 'default' | 'vite';

  /**
   * add `.js` extensions to imports, needed for ESM without bundler resolution
   */
  addJsExtension?: boolean;

  /**
   * Generate entry point for browser
   * @defaultValue true
   */
  browser?: boolean;

  /**
   * Generate entry point for dynamic compilation
   * @defaultValue true
   */
  dynamic?: boolean;
}
```
Sources: [packages/mdx/src/plugins/index-file.ts:13-32](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/plugins/index-file.ts#L13-L32)

| Option Key | Type | Default Value | Description |
| :--- | :--- | :--- | :--- |
| `target` | `'default' \| 'vite'` | `'default'` | Target bundler environment for codegen paths and glob resolution. |
| `addJsExtension` | `boolean` | `undefined` | Appends `.js` extensions to imports for ESM without bundler resolution. |
| `browser` | `boolean` | `true` | Generates a browser entry point module (`browser.ts`). |
| `dynamic` | `boolean` | `true` | Generates an entry point for dynamic content compilation (`dynamic.ts`). |

Sources: [packages/mdx/src/plugins/index-file.ts:13-32](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/plugins/index-file.ts#L13-L32)

## Webpack Loader Interception

### Standalone MDX and Meta Loaders

The package exposes standalone Webpack loaders for processing MDX documents and collection metadata files outside of the Next.js plugin wrapper. Both loaders utilize a lazy-initialized singleton instance via `toWebpack` to bind core loader logic, while declaring dependencies on compiled configuration paths via Webpack's `this.addDependency()`.

```typescript
const loader: LoaderDefinitionFunction<WebpackLoaderOptions> = function loader(source) {
  const callback = this.async();
  const options = this.getOptions();
  this.cacheable(true);
  this.addDependency(options.absoluteCompiledConfigPath);

  if (!instance) {
    instance = toWebpack(
      createMdxLoader(
        createStandaloneConfigLoader({
          core: getCore(options),
          buildConfig: false,
          mode: options.isDev ? 'dev' : 'production',
        }),
      ),
    );
  }

  void instance.call(this, source, callback);
};
```
Sources: [packages/mdx/src/webpack/mdx.ts:9-28](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/webpack/mdx.ts#L9-L28)

```typescript
const loader: LoaderDefinitionFunction<WebpackLoaderOptions> = function (source) {
  const callback = this.async();
  const options = this.getOptions();
  this.cacheable(true);
  this.addDependency(options.absoluteCompiledConfigPath);

  if (!instance) {
    instance = toWebpack(
      createMetaLoader(
        createStandaloneConfigLoader({
          core: getCore(options),
          buildConfig: false,
          mode: options.isDev ? 'dev' : 'production',
        }),
        {
          json: 'json',
          yaml: 'js',
        },
      ),
    );
  }

  void instance.call(this, source, callback);
};
```
Sources: [packages/mdx/src/webpack/meta.ts:9-32](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/webpack/meta.ts#L9-L32)

> [!NOTE]
> The meta loader configures `createMetaLoader` with mapping values `{ json: 'json', yaml: 'js' }`, passing an options object to `createStandaloneConfigLoader` where `buildConfig` is disabled and `mode` is derived from `options.isDev`.
Sources: [packages/mdx/src/webpack/meta.ts:17-27](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/webpack/meta.ts#L17-L27)

### Story File Transformation Loader

The story loader handles component documentation files by leveraging `ts-morph` projects and file transformation utilities. It accepts options via `StoryLoaderOptions` and manages asynchronous execution pools through `projectPromise`.

```typescript
export interface StoryLoaderOptions {
  /**
   * Path to `tsconfig.json`.
   *
   * @default "tsconfig.json"
   */
  tsconfigPath?: string;
}

let projectPromise: Promise<Project> | undefined;

const loader: LoaderDefinitionFunction<StoryLoaderOptions> = function (source) {
  const callback = this.async();
  const options = this.getOptions();
  this.cacheable(true);

  const run = async () => {
    if (!projectPromise) {
      const resolvedTsconfig = options.tsconfigPath ?? path.join(this.rootContext, 'tsconfig.json');
      projectPromise = createControlsProject(resolvedTsconfig);
    }

    try {
      const transformed = transformStoryFile(
        '@fumadocs/story/next/client',
        source,
        this.resourcePath,
        await projectPromise!,
      );
      callback(undefined, transformed ?? source);
    } catch (error) {
      if (!(error instanceof Error)) throw error;
      callback(error);
    }
  };

  void run();
};
```
Sources: [packages/story/src/webpack/story.ts:7-44](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/story/src/webpack/story.ts#L7-L44)

| Option Key | Type | Default Value | Description |
| :--- | :--- | :--- | :--- |
| `tsconfigPath` | `string` | `"tsconfig.json"` | Path to the `tsconfig.json` configuration file used for `ts-morph` project initialization. |

Sources: [packages/story/src/webpack/story.ts:7-14](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/story/src/webpack/story.ts#L7-L14), [packages/story/src/webpack/story.ts:25-25](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/story/src/webpack/story.ts#L25-L25)

> [!WARNING]
> If `projectPromise` has not been initialized when a story file is processed, the loader resolves `tsconfigPath` relative to `this.rootContext` before creating the controls project.
Sources: [packages/story/src/webpack/story.ts:23-27](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/story/src/webpack/story.ts#L23-L27)

## Vite and Rolldown Adapters

### Vite Plugin Architecture and Server Lifecycle

The Vite plugin wrapper orchestrates core compilation lifecycle hooks, configuration building, and development server file watching. It exposes `PluginOptions` to control index generation, configuration paths, module resolution updates, and output directories.

```typescript
export interface PluginOptions {
  /**
   * Generate index files for accessing content.
   *
   * @defaultValue true
   */
  index?: boolean | IndexFilePluginOptions;

  /**
   * @defaultValue source.config.ts
   */
  configPath?: string;

  /**
   * Update Vite config to fix module resolution of Fumadocs
   *
   * @defaultValue true
   */
  updateViteConfig?: boolean;

  /**
   * Output directory of generated files
   *
   * @defaultValue '.source'
   */
  outDir?: string;
}
```
Sources: [packages/mdx/src/vite/index.ts:12-38](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/vite/index.ts#L12-L38)

| Option Key | Type | Default Value | Description |
| :--- | :--- | :--- | :--- |
| `index` | `boolean \| IndexFilePluginOptions` | `true` | Generates index files for accessing content or configures index file options. |
| `configPath` | `string` | `'source.config.ts'` | Path to the content source configuration file. |
| `updateViteConfig` | `boolean` | `true` | Updates Vite config to fix module resolution of Fumadocs. |
| `outDir` | `string` | `'.source'` | Output directory of generated files. |

Sources: [packages/mdx/src/vite/index.ts:12-38](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/vite/index.ts#L12-L38)

During the `config` hook, the plugin initializes core functionality via `createViteCore` and runs `core.init()`. It registers `pre`-ordered transform hooks for MDX and meta loaders while establishing file watchers on the configuration path.

> [!CAUTION]
> When Vite RSC processes client references, it passes compiled MDX client modules with IDs matching `virtual:vite-rsc`. The plugin explicitly intercepts and ignores these IDs to prevent JavaScript content formatting from breaking the MDX compiler.
Sources: [packages/mdx/src/vite/index.ts:83-86](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/vite/index.ts#L83-L86)

```typescript
return [
  {
    name: 'fumadocs-mdx',
    async config(config) {
      root = config.root ?? process.cwd();
      options = applyDefaults(root, pluginOptions);
      core = createViteCore(options);
      await core.init({
        config: buildConfig(
          forcedConfig
            ? await forcedConfig
            : (await runnerImport<Record<string, unknown>>(options.configPath)).module,
          root,
        ),
      });

      const configLoader = createIntegratedConfigLoader(core);
      const mdxLoader = toVite(createMdxLoader(configLoader));
      const metaLoader = toVite(
        createMetaLoader(configLoader, {
          // vite has built-in plugin for JSON files
          json: 'json',
        }),
      );

      mdxPlugin.transform = {
        filter: mdxLoader.filter,
        order: 'pre',
        handler(code, id) {
          // Vite RSC will pass the compiled MDX file's client module with ID `virtual:vite-rsc/client-references/group/facade:xxx.mdx`.
          // The format of `value` becomes JavaScript, which will break the MDX compiler.
          // We have to ignore them.
          if (id.includes('virtual:vite-rsc')) return null;
          if (!forcedConfig) this.addWatchFile(options.configPath);
          return mdxLoader.transform.call(this, code, id);
        },
      };
      metaPlugin.transform = {
        filter: metaLoader.filter,
        order: 'pre',
        handler(code, id) {
          if (!forcedConfig) this.addWatchFile(options.configPath);
          return metaLoader.transform.call(this, code, id);
        },
      };

      if ('_fumadocs_skipViteConfig' in config && config._fumadocs_skipViteConfig) return;
      if (!options.updateViteConfig) return;

      const { getConfig } = await import('@fumadocs/vite');
      return getConfig({ root });
    },
```
Sources: [packages/mdx/src/vite/index.ts:54-105](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/vite/index.ts#L54-L105)

### Rolldown Plugin Configuration and File Loading

The Rolldown plugin wrapper configures core compilation with a `rolldown` environment parameter. It constructs integrated configuration and loaders, returning a set of Rolldown plugins that handle raw file loading and transformation.

```typescript
export interface PluginOptions {
  configPath?: string;
  outDir?: string;
}

export default async function mdx(
  config: Record<string, unknown> | Promise<Record<string, unknown>>,
  options?: PluginOptions,
): Promise<RolldownPlugin[]> {
  const core = createCore({
    configPath: options?.configPath ?? _Defaults.configPath,
    outDir: options?.outDir ?? _Defaults.outDir,
    environment: 'rolldown',
  });
  await core.init({
    config: buildConfig(await config, process.cwd()),
  });

  const configLoader = createIntegratedConfigLoader(core);
  const mdxLoader = toVite(createMdxLoader(configLoader));
  const metaLoader = toVite(
    createMetaLoader(configLoader, {
      // rolldown has built-in plugin for JSON files
      json: 'json',
    }),
  );

```
Sources: [packages/mdx/src/rolldown/index.ts:11-37](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/rolldown/index.ts#L11-L37)

| Option Key | Type | Default Value | Description |
| :--- | :--- | :--- | :--- |
| `configPath` | `string` | `_Defaults.configPath` | Path to the content source configuration file. |
| `outDir` | `string` | `_Defaults.outDir` | Output directory of generated files. |

Sources: [packages/mdx/src/rolldown/index.ts:11-14](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/rolldown/index.ts#L11-L14), [packages/mdx/src/rolldown/index.ts:21-22](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/rolldown/index.ts#L21-L22)

> [!NOTE]
> Because Rolldown cannot read the correct file path when query parameters exist on module IDs, a dedicated load handler strips query strings using `id.lastIndexOf('?')` before reading the file contents from disk via `fs.readFile`.
Sources: [packages/mdx/src/rolldown/index.ts:41-50](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/rolldown/index.ts#L41-L50)

```typescript
return [
  {
    name: 'fumadocs-mdx',
    load: {
      filter: { id: [metaLoaderGlob, mdxLoaderGlob] },
      // Rolldown couldn't read the correct file path when query params exist.
      async handler(id) {
        const idx = id.lastIndexOf('?');
        if (idx === -1) return null;

        return fs.readFile(id.slice(0, idx), 'utf-8');
      },
    },
  },
  {
    name: 'fumadocs-mdx:mdx',
    transform: {
      filter: mdxLoader.filter,
      handler: mdxLoader.transform,
    },
  },
  {
    name: 'fumadocs-mdx:meta',
    transform: {
      filter: metaLoader.filter,
      handler: metaLoader.transform,
    },
  },
];
```
Sources: [packages/mdx/src/rolldown/index.ts:38-66](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/rolldown/index.ts#L38-L66)

### Story File Transformation Plugin

The story plugin provides a Vite-compatible transformation pipeline for component stories, leveraging `ts-morph` projects and filtering file IDs against regular expressions.

```typescript
export interface StoryPluginOptions {
  /**
   * Path to `tsconfig.json`.
   *
   * @default "tsconfig.json"
   */
  tsconfigPath?: string;

  /**
   * Filter story files to transform by id (regex)
   *
   * @default `/\.story\.(js|jsx|ts|tsx)$/`
   */
  filter?: RegExp;
}
```
Sources: [packages/story/src/vite/index.ts:7-21](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/story/src/vite/index.ts#L7-L21)

| Option Key | Type | Default Value | Description |
| :--- | :--- | :--- | :--- |
| `tsconfigPath` | `string` | `"tsconfig.json"` | Path to the `tsconfig.json` configuration file. |
| `filter` | `RegExp` | `/\.story\.(js\|jsx\|ts\|tsx)$/` | Filter story files to transform by ID regex. |

Sources: [packages/story/src/vite/index.ts:7-21](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/story/src/vite/index.ts#L7-L21)

```typescript
export default function story(pluginOptions: StoryPluginOptions = {}): Plugin {
  const { filter = /\.story\.(js|jsx|ts|tsx)$/ } = pluginOptions;
  let tsconfigPath: string;
  let projectPromise: Promise<Project> | undefined;

  return {
    name: 'fumadocs:story',
    enforce: 'pre',
    configResolved(resolved) {
      tsconfigPath = pluginOptions.tsconfigPath ?? path.join(resolved.root, 'tsconfig.json');
    },
    transform: {
      filter: { id: filter },
      async handler(code, id) {
        projectPromise ??= createControlsProject(tsconfigPath);

        return transformStoryFile('@fumadocs/story/vite/client', code, id, await projectPromise);
      },
    },
  };
```
Sources: [packages/story/src/vite/index.ts:23-42](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/story/src/vite/index.ts#L23-L42)

## Node and Bun Runtime Loaders

### Bun Plugin Module Interception

The Bun plugin integration configures core settings and exposes module loading hooks tailored for the Bun runtime environment. It implements the `BunPlugin` interface through `createMdxPlugin`, accepting options that extend partial core configurations alongside custom plugin toggles.

| Option Key | Type | Default Value | Description |
| :--- | :--- | :--- | :--- |
| `environment` | `string` | `'bun'` | Runtime execution environment identifier. |
| `outDir` | `string` | `_Defaults.outDir` | Output directory path for generated files. |
| `configPath` | `string` | `_Defaults.configPath` | Path to the content source configuration file. |
| `disableMetaFile` | `boolean` | `false` | Disables the automatic meta file transformation step. |

Sources: [packages/mdx/src/bun/index.ts:10-23](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/bun/index.ts#L10-L23)

During the `setup` lifecycle hook, the plugin initializes core functionality by transforming the configuration path into a file URL and invoking `core.init()`. It establishes integrated configuration loading and assigns MDX and meta loaders using the Bun adapter utility.

```typescript
  return {
    name: 'bun-plugin-fumadocs-mdx',
    async setup(build) {
      const importPath = pathToFileURL(configPath).href;
      const core = createCore({
        environment,
        outDir,
        configPath,
      });

      await core.init({
        config: buildConfig(await import(importPath), process.cwd()),
      });

      const configLoader = createIntegratedConfigLoader(core);
      toBun(createMdxLoader(configLoader))(build);
      if (!disableMetaFile) toBun(createMetaLoader(configLoader))(build);
    },
  };
}
```
Sources: [packages/mdx/src/bun/index.ts:25-44](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/bun/index.ts#L25-L44)

> [!NOTE]
> The meta loader step can be entirely bypassed by passing `disableMetaFile: true` in the plugin options, preventing unnecessary processing of metadata files when only core MDX compilation is required.
Sources: [packages/mdx/src/bun/index.ts:14-22](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/bun/index.ts#L14-L22), [packages/mdx/src/bun/index.ts:41-41](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/bun/index.ts#L41-L41)

### Native Node.js Module Loaders

Native Node.js environments utilize custom module loading hooks conforming to Node's module customization hooks specification. The modern initialization implementation defines an `initialize` hook that sets up standalone configuration loaders for production mode.

| Option Key | Type | Default Value | Description |
| :--- | :--- | :--- | :--- |
| `environment` | `string` | `'node-loader'` | Execution environment assigned during core creation. |
| `configPath` | `string` | `_Defaults.configPath` | Path to the content source configuration file. |
| `outDir` | `string` | `_Defaults.outDir` | Output directory path for generated content. |
| `disableMetaFile` | `boolean` | `undefined` | Conditionally skips pushing the meta loader into the loader cache. |

Sources: [packages/mdx/src/node/_loader.ts:11-29](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/node/_loader.ts#L11-L29)

```typescript
export const initialize: InitializeHook<NodeLoaderOptions> = (options) => {
  const core = createCore({
    environment: 'node-loader',
    ...options,
    configPath: options.configPath ?? _Defaults.configPath,
    outDir: options.outDir ?? _Defaults.outDir,
  });

  const configLoader = createStandaloneConfigLoader({
    core,
    buildConfig: true,
    mode: 'production',
  });

  cachedLoaders = [toNode(createMdxLoader(configLoader))];
  if (!options.disableMetaFile) {
    cachedLoaders.push(toNode(createMetaLoader(configLoader)));
  }
};

```
Sources: [packages/mdx/src/node/_loader.ts:11-30](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/node/_loader.ts#L11-L30)

The accompanying `load` hook iterates sequentially through cached Node module loaders using a recursive runner function, falling back to the standard `nextLoad` function when all custom hooks have executed.

```typescript
export const load: LoadHook = async (url, context, nextLoad) => {
  if (!cachedLoaders) throw new Error('not initialized');
  const hooks = cachedLoaders;

  function run(
    i: number,
    url: string,
    context: LoadHookContext,
  ): LoadFnOutput | Promise<LoadFnOutput> {
    if (i >= hooks.length) {
      return nextLoad(url, context);
    }

    return hooks[i](url, context, (url, ctx) => run(i + 1, url, { ...context, ...ctx }));
  }

  return run(0, url, context);
};
```
Sources: [packages/mdx/src/node/_loader.ts:31-48](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/node/_loader.ts#L31-L48)

> [!WARNING]
> Attempting to invoke the `load` hook before the module loader has been properly initialized via `initialize` triggers an immediate runtime error stating `not initialized`.
Sources: [packages/mdx/src/node/_loader.ts:31-32](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/node/_loader.ts#L31-L32)

## Related

- [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.
