Skip to main content
The plugin system allows you to extend Fumadocs MDX with custom transformations, file generation, and dev server features.

Plugin Interface

interface Plugin {
  name?: string;
  config?: (this: PluginContext, config: LoadedConfig) => Awaitable<LoadedConfig | void>;
  emit?: (this: PluginContext) => Awaitable<EmitEntry[]>;
  configureServer?: (this: PluginContext, server: ServerContext) => Awaitable<void>;
  meta?: {
    transform?: (this: CompilationContext<MetaCollectionItem>, data: unknown) => Awaitable<unknown | void>;
  };
  doc?: {
    frontmatter?: (this: CompilationContext<DocCollectionItem>, data: Record<string, unknown>) => Awaitable<Record<string, unknown> | void>;
    vfile?: (this: CompilationContext<DocCollectionItem>, file: VFile) => Awaitable<VFile | void>;
  };
}
name
string
Plugin identifier for debugging and filtering
config
(config: LoadedConfig) => LoadedConfig | void
Called when configuration is loaded. Return modified config or void
emit
() => Promise<EmitEntry[]>
Generate files (types, schemas, index files). Return array of file entries
configureServer
(server: ServerContext) => Promise<void>
Configure the development server and file watchers
meta.transform
(data: unknown) => unknown | void
Transform metadata from JSON/YAML files
doc.frontmatter
(data: Record<string, unknown>) => Record<string, unknown> | void
Transform MDX frontmatter before compilation
doc.vfile
(file: VFile) => VFile | void
Transform the VFile during MDX compilation

Built-in Plugins

indexFile

Generates index files for accessing collections.
import indexFile from 'fumadocs-mdx/plugins/index-file';
import { defineConfig } from 'fumadocs-mdx/config';

export default defineConfig({
  plugins: [
    indexFile({
      target: 'default',
      addJsExtension: false,
      browser: true,
      dynamic: true
    })
  ]
});
target
'default' | 'vite'
default:"'default'"
  • default: Generate static imports
  • vite: Use import.meta.glob for better HMR
addJsExtension
boolean
default:"false"
Add .js extensions to imports for ESM compatibility
browser
boolean
default:"true"
Generate browser entry point (browser.ts)
dynamic
boolean
default:"true"
Generate dynamic compilation entry point (dynamic.ts)

Generated Files

// .source/server.ts
import { docs } from '.source/server';

for (const page of docs.getPages()) {
  console.log(page.url, page.data.title);
}
// .source/browser.ts
import collections from '.source/browser';

const page = await collections.docs.load('introduction');
// .source/dynamic.ts
import { docs } from '.source/dynamic';

// Compiles MDX on-demand
const page = await docs.load('introduction');

jsonSchema

Generates JSON schemas for collection schemas.
import jsonSchema from 'fumadocs-mdx/plugins/json-schema';
import { defineConfig } from 'fumadocs-mdx/config';

export default defineConfig({
  plugins: [
    jsonSchema({
      insert: true
    })
  ]
});
insert
boolean
default:"false"
Automatically insert $schema field when creating new JSON files
Generates .source/json-schema/{collection}.json files for IDE autocomplete:
{
  "$schema": "../../.source/json-schema/docs.meta.json",
  "title": "API Reference"
}

lastModified

Injects last modified timestamps from version control.
import lastModified from 'fumadocs-mdx/plugins/last-modified';
import { defineConfig } from 'fumadocs-mdx/config';

export default defineConfig({
  plugins: [
    lastModified({
      versionControl: 'git',
      filter: (collection) => collection !== 'blog'
    })
  ]
});
versionControl
'git' | ((filePath: string) => Promise<Date | null>)
default:"'git'"
Version control system or custom function to get timestamps
filter
(collection: string) => boolean
Filter which collections to include
Access the timestamp:
import { lastModified } from './page.mdx';

export default function Page() {
  return <time>{lastModified?.toLocaleDateString()}</time>;
}

Remark Plugins

remarkInclude

Include external files and code snippets.
import { defineConfig, remarkInclude } from 'fumadocs-mdx/config';

export default defineConfig({
  mdxOptions: {
    remarkPlugins: [remarkInclude]
  }
});

Syntax

Include entire files:
<include>./example.tsx</include>
Include specific sections:
<include>./example.tsx#region-name</include>
Customize code block:
<include lang="typescript" meta="title=\"Example\"">
./example.js
</include>

Region Syntax

Supports multiple region markers:
// #region feature
function example() {
  return 'This will be included';
}
// #endregion feature
<!-- #region template -->
<div>This content will be included</div>
<!-- #endregion template -->
lang
string
Override language for syntax highlighting
meta
string
Add metadata to code block
cwd
string
Custom working directory for resolving paths

remarkPostprocess

Post-processes MDX content for extraction and export.
import { remarkPostprocess } from 'fumadocs-mdx/loaders/mdx/remark-postprocess';

// Applied automatically via postprocess config
Configured through collection settings:
export const docs = defineCollections({
  type: 'doc',
  dir: 'content',
  postprocess: {
    includeProcessedMarkdown: true,
    extractLinkReferences: true,
    includeMDAST: { removePosition: true }
  }
});

Creating Custom Plugins

Basic Plugin

import type { Plugin } from 'fumadocs-mdx/core';

export function myPlugin(): Plugin {
  return {
    name: 'my-plugin',
    doc: {
      frontmatter(data) {
        // Add default values
        return {
          ...data,
          category: data.category ?? 'general'
        };
      }
    }
  };
}

File Generation Plugin

import type { Plugin } from 'fumadocs-mdx/core';

export function manifestPlugin(): Plugin {
  return {
    name: 'manifest',
    async emit() {
      const collections = this.core.getCollections();
      const manifest = {
        collections: collections.map(c => c.name),
        timestamp: new Date().toISOString()
      };

      return [
        {
          path: 'manifest.json',
          content: JSON.stringify(manifest, null, 2)
        }
      ];
    }
  };
}

Dev Server Plugin

import type { Plugin } from 'fumadocs-mdx/core';

export function watchPlugin(): Plugin {
  return {
    name: 'watch',
    configureServer(server) {
      server.watcher?.on('change', (file) => {
        console.log('File changed:', file);
      });
    }
  };
}

VFile Transform Plugin

import type { Plugin } from 'fumadocs-mdx/core';
import { visit } from 'unist-util-visit';

export function imageOptimizer(): Plugin {
  return {
    name: 'image-optimizer',
    doc: {
      async vfile(file) {
        visit(file.data.mdast, 'image', (node) => {
          // Transform image URLs
          node.url = `/optimized/${node.url}`;
        });
        return file;
      }
    }
  };
}

Plugin Context

Plugins receive context with access to the core system.
interface PluginContext {
  core: Core;
}
core
Core
Core instance with configuration and collection access

Core Methods

interface Core {
  getOptions(): CoreOptions;
  getConfig(): LoadedConfig;
  getCollections(): CollectionItem[];
  getCollection(name: string): CollectionItem | undefined;
  getWorkspaces(): Map<string, Core>;
  transformMeta(options: TransformOptions, data: unknown): Promise<unknown>;
  transformFrontmatter(options: TransformOptions, data: Record<string, unknown>): Promise<Record<string, unknown>>;
  transformVFile(options: TransformOptions, file: VFile): Promise<VFile>;
}

Compilation Context

Document and metadata hooks receive compilation context:
interface CompilationContext<Collection> {
  core: Core;
  collection: Collection;
  filePath: string;
  source: string;
}
core
Core
Core instance
collection
DocCollectionItem | MetaCollectionItem
The collection being processed
filePath
string
Absolute path to the file
source
string
Raw file content

Server Context

interface ServerContext {
  watcher?: FSWatcher;
}
watcher
FSWatcher
Chokidar file watcher instance. Filter events before responding

Example: Custom Watcher

export function customWatcher(): Plugin {
  return {
    name: 'custom-watcher',
    configureServer(server) {
      server.watcher?.on('all', (event, file) => {
        if (file.endsWith('.custom')) {
          console.log('Custom file changed:', file);
        }
      });
    }
  };
}

Emit Entry

interface EmitEntry {
  path: string;
  content: string;
}
path
string
required
File path relative to output directory
content
string
required
File contents to write

Type Extensions

Extend generated types through the index-file plugin hook:
import type { Plugin } from 'fumadocs-mdx/core';

export function customTypes(): Plugin {
  return {
    name: 'custom-types',
    'index-file': {
      generateTypeConfig() {
        return `{
          DocData: {
            docs: {
              customField: string;
            }
          }
        }`;
      },
      serverOptions(options) {
        options.doc ??= {};
        options.doc.passthroughs ??= [];
        options.doc.passthroughs.push('customField');
      }
    }
  };
}

Build docs developers (and LLMs) love