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>;
};
}
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
Add .js extensions to imports for ESM compatibility
Generate browser entry point (browser.ts)
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
})
]
});
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>;
}
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 -->
Override language for syntax highlighting
Add metadata to code block
Custom working directory for resolving paths
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);
});
}
};
}
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 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;
}
collection
DocCollectionItem | MetaCollectionItem
The collection being processed
Absolute path to the file
Server Context
interface ServerContext {
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;
}
File path relative to output directory
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');
}
}
};
}