Macro API
Define collections in your app with fumadocs-mdx/macro.
Overview
Declare collections directly in your application modules with fumadocs-mdx/macro.
The bundler (or runtime loader) compiles defineDocs / defineCollections into imports of content files.
- No codegen needed.
- No
source.config.tsneeded.
Usage
import { defineDocs } from 'fumadocs-mdx/macro';
import { loader } from 'fumadocs-core/source';
export const docs = defineDocs({
dir: 'content/docs',
});
export const source = loader({
baseUrl: '/docs',
source: docs.toFumadocsSource(),
});Collection options are largely the same as the config API.
You can still keep a source.config.ts for global options and plugins while defining collections with macros.
Browser
Macro collections work in the browser too. Import the collection in a client component and render an entry with body, no client loader needed.
import { docs } from '@/lib/source';
export default function Page({ path }: { path: string }) {
const page = docs.getPage(path);
if (!page) return <div>Not Found</div>;
const MDX = page.body;
return (
<div className="prose">
<h1>{page.title}</h1>
<MDX />
</div>
);
}Async collections
With async enabled, only the frontmatter is bundled upfront and the content is fetched on demand.
body works the same way, except it suspends until the content is available. Two extra methods control that:
preload()loads the content ahead of rendering, sobodyrenders without suspending. Call it in your route loader.load()returns the loaded content, such astoc. Its promise is cached, so you can call it during render.
import { use } from 'react';
import { docs } from '@/lib/source';
// in your route loader (React Router, Tanstack Start)
export async function loader({ path }: { path: string }) {
await docs.getPage(path)?.preload();
return { path };
}
export default function Page({ path }: { path: string }) {
const page = docs.getPage(path);
if (!page) return <div>Not Found</div>;
const { toc } = use(page.load());
const MDX = page.body;
return (
<div className="prose">
<h1>{page.title}</h1>
<MDX />
</div>
);
}Since the content is loaded lazily, wrap the page in a <Suspense> boundary unless you preloaded it.
Configuration
The Macro API is enabled by default. Pass macro to limit which modules may use it, or false to disable it.
import { createMDX } from 'fumadocs-mdx/next';
const withMDX = createMDX({
macro: {
// glob patterns relative to the project root
include: ['lib/**/*.ts', 'lib/**/*.tsx'],
},
// or disable entirely:
// macro: false,
});By default, all JS/TS modules are eligible (node_modules is always excluded). Narrow include if you want the transform to run only on specific paths.
Constraints
Macro calls are compiled statically. Keep these rules in mind:
-
Assign each call to a top-level
const(the variable name identifies the collection):// ✓ const docs = defineDocs({ dir: 'content/docs' }); // ✗ not supported export function create() { return defineDocs({ dir: 'content/docs' }); } -
Options that shape how content is bundled (
dir,files,async) must be statically analyzable (string / boolean literals). Put dynamic logic in options likeschemaormdxOptionsinstead. -
dynamiccollections are not supported by the Macro API yet. Preferasyncfor lazy loading. -
Re-exporting from
fumadocs-mdx/macrois not supported.
How is this guide?
Last updated on
