Technical Documentation: fuma-nama/fumadocs
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
Overview (README.md)
The framework for building documentation websites in React.js frameworks and Astro.
Officially Supported:
- Next.js
- Astro (with React)
- Vite: Tanstack Start, Waku, React Router
📘 Learn More: Documentation.
Compatibility
All packages are ESM only.
Sticker
Welcome to print it out :D
Contributions
Make sure to read the Contributing Guide before submitting a pull request.
Examples Astro Content Docs Index (`examples/astro/content/docs/index.mdx`)
title: Hello World
Welcome to the docs! You can start writing documents in /content/docs.
console.log('Hello World');What's next?
Examples Asyncapi Content Docs Index (`examples/asyncapi/content/docs/index.mdx`)
title: Overview
Scalar Galaxy Events is an AsyncAPI 3.0 specification that documents real-time notifications and event-driven capabilities for the Scalar Galaxy API.
Event-Driven Features
- Planet Updates: Get notified when planets are created, updated, or experience cosmic events
- User Activity Streams: Track user signups, authentication events, and profile changes
- Celestial Body Monitoring: Monitor changes to satellites, asteroids, and other celestial objects
- System Notifications: Receive alerts about system maintenance, rate limits, and security events
Operations
AsyncAPI operations use send and receive actions instead of HTTP methods. Each operation references a channel and the messages it can process.
Browse the generated reference pages under References to explore channels, messages, bindings, and security schemes from the spec.
Examples Base Ui Content Docs Index (`examples/base-ui/content/docs/index.mdx`)
title: Hello World
Welcome to the docs! You can start writing documents in /content/docs.
What is Next?
Examples Content Collections Content Docs Index (`examples/content-collections/content/docs/index.mdx`)
title: Hello World
Hey there!
Heading
Heading
Heading
| name | description |
|---|---|
| Hello World | Goodbye |
Examples Graphql Content Docs Index (`examples/graphql/content/docs/index.mdx`)
title: Overview
This example documents a GraphQL schema with Fumadocs GraphQL, generated from a single SDL file (schema.graphql) and example operations (examples/store.graphql).
API Reference
Browse the generated reference pages under API Reference:
- Queries, Mutations, Subscriptions: a page per operation with its arguments, return type, code samples and examples.
- Objects, Interfaces, Unions, Enums, Input Objects, Scalars: a page per named type, enabled with
includeTypes: trueinlib/source.ts.
Playground
Open Try in Playground on an operation page, then press Send to send the operation to the endpoint.
This example ships a mock endpoint at /api/graphql that responds with sample values, replace it with your own GraphQL server.
Examples I18n Content Docs Index (`examples/i18n/content/docs/index.mdx`)
title: Hello World
Hey there!
Heading
Heading
Heading
Examples Next Local Md Content Docs Index (`examples/next-local-md/content/docs/index.mdx`)
title: Hello World
Welcome to the docs! You can start writing documents in /content/docs.
What is Next?
Examples Next Min Content Docs Index (`examples/next-min/content/docs/index.mdx`)
title: Hello World
Your first document
console.log("Hello World")Hey there! fsd asfd sdfsfda
sfd
fdsa ff dsaf sdf sda fasd fsd
Heading
Hello World dsasfdafsd
Heading
Heading
| Head | Description |
|---|---|
hello |
Hello World |
| very important | Hey |
| Surprisingly | Fumadocs |
| very long text that looks weird | hello world hello world hello world |
./test.mdx
Examples Next Shadcn Content Docs Index (`examples/next-shadcn/content/docs/index.mdx`)
title: Hello World
Your first document
console.log("Hello World")Hey there! fsd asfd sdfsfda
sfd
fdsa ff dsaf sdf sda fasd fsd
Heading
Hello World dsasfdafsd
Heading
Heading
| Head | Description |
|---|---|
hello |
Hello World |
| very important | Hey |
| Surprisingly | Fumadocs |
| very long text that looks weird | hello world hello world hello world |
Examples Next Static Content Docs Index (`examples/next-static/content/docs/index.mdx`)
title: Hello World
Welcome to the docs! You can start writing documents in /content/docs.
What is Next?
Examples Next Content Docs Index (`examples/next/content/docs/index.mdx`)
title: Hello World
Welcome to the docs! You can start writing documents in /content/docs.
What is Next?
Examples Openapi Content Docs Index (`examples/openapi/content/docs/index.mdx`)
title: Overview
The Unkey API uses HTTP RPC-style methods and generally follow the schema:
https://api.unkey.dev/{version}/{service}.{method}For example GET https://api.unkey.dev/v1/apis.listKeys to list all keys for an API.
HTTP Methods
We strictly use only GET and POST methods. PUT and DELETE are not used.
`GET`
GET methods are used for reading data. Filtering, sorting, or pagination is done via query parameters.
curl "https://api.unkey.dev/v1/keys.getKey?keyId=key_123" \
-H "Authorization: Bearer <ROOT_KEY>"`POST`
POST methods are used for creating, updating, and deleting data. Data is passed as application/json in the request body.
curl -XPOST "https://api.unkey.dev/v1/keys.createKey" \
-H "Authorization: Bearer <ROOT_KEY>" \
-H "Content-Type: application/json" \
-d '{"apiId": "api_123", "name": "My Key"}'Examples React Router I18n Content Docs Index (`examples/react-router-i18n/content/docs/index.mdx`)
title: Hello World Your first `document` You'll love it!
Hey there! Fumadocs is the docs framework that also works on React Router!
Heading
Hello World
console.log('I love React!');Heading
Heading
| Head | Description |
|---|---|
hello |
Hello World |
| very important | Hey |
| Surprisingly | Fumadocs |
| very long text that looks weird | hello world hello world hello world |
Examples React Router Min Content Docs Index (`examples/react-router-min/content/docs/index.mdx`)
title: Hello World Your first `document` You'll love it!
Hey there! Fumadocs is the docs framework that also works on React Router!
Heading
Hello World
console.log('I love React!');Heading
Heading
| Head | Description |
|---|---|
hello |
Hello World |
| very important | Hey |
| Surprisingly | Fumadocs |
| very long text that looks weird | hello world hello world hello world |
Examples React Router Rsc Content Docs Index (`examples/react-router-rsc/content/docs/index.mdx`)
title: Hello World Your first `document` You'll love it!
Hey there! Fumadocs is the docs framework that also works on React Router!
Heading
Hello World
console.log('I love React!');Heading
Heading
| Head | Description |
|---|---|
hello |
Hello World |
| very important | Hey |
| Surprisingly | Fumadocs |
| very long text that looks weird | hello world hello world hello world |
Examples React Router Spa Content Docs Index (`examples/react-router-spa/content/docs/index.mdx`)
title: Hello World Your first `document` You'll love it!
Hey there! Fumadocs is the docs framework that also works on React Router!
Heading
Hello World
console.log('I love React!');Heading
Heading
| Head | Description |
|---|---|
hello |
Hello World |
| very important | Hey |
| Surprisingly | Fumadocs |
| very long text that looks weird | hello world hello world hello world |
Examples React Router Content Docs Index (`examples/react-router/content/docs/index.mdx`)
title: Hello World Your first `document` You'll love it!
Hey there! Fumadocs is the docs framework that also works on React Router!
Heading
Hello World
console.log('I love React!');Heading
Heading
| Head | Description |
|---|---|
hello |
Hello World |
| very important | Hey |
| Surprisingly | Fumadocs |
| very long text that looks weird | hello world hello world hello world |
Examples Stackblitz Content Docs Index (`examples/stackblitz/content/docs/index.mdx`)
title: Hello World icon: Rocket
Hey there! Fumadocs is a docs framework that works on Waku!
Heading
Hello World!
CodeBlock
console.log('Hello World');Table
| Head | Description |
|---|---|
hello |
Hello World |
| very important | Hey |
| Surprisingly | Fumadocs |
| very long text that looks weird | hello world hello world hello world |
Examples Tanstack Start I18n Content Docs Index (`examples/tanstack-start-i18n/content/docs/index.mdx`)
title: Hello World icon: Rocket
Hey there! Fumadocs is a docs framework built for Next.js, but do you know it also works on Tanstack Start?
Heading
Hello World!
CodeBlock
console.log('Hello World');Table
| Head | Description |
|---|---|
hello |
Hello World |
| very important | Hey |
| Surprisingly | Fumadocs |
| very long text that looks weird | hello world hello world hello world |
Examples Tanstack Start Local Md Content Docs Index (`examples/tanstack-start-local-md/content/docs/index.mdx`)
title: Hello World icon: Rocket
Hey there! Fumadocs is the docs framework that also works on Tanstack Start!
Heading
Hello World!
CodeBlock
console.log('Hello World');Table
| Head | Description |
|---|---|
hello |
Hello World |
| very important | Hey |
| Surprisingly | Fumadocs |
| very long text that looks weird | hello world hello world hello world |
Examples Tanstack Start Min Content Docs Index (`examples/tanstack-start-min/content/docs/index.mdx`)
title: Hello World icon: Rocket
Hey there! Fumadocs is the docs framework that also works on Tanstack Start!
Heading
Hello World!
CodeBlock
console.log('Hello World');Table
| Head | Description |
|---|---|
hello |
Hello World |
| very important | Hey |
| Surprisingly | Fumadocs |
| very long text that looks weird | hello world hello world hello world |
Examples Tanstack Start Openapi Content Docs Index (`examples/tanstack-start-openapi/content/docs/index.mdx`)
title: Hello World icon: Rocket
Hey there! Fumadocs is the docs framework that also works on Tanstack Start!
Heading
Hello World!
CodeBlock
console.log('Hello World');Table
| Head | Description |
|---|---|
hello |
Hello World |
| very important | Hey |
| Surprisingly | Fumadocs |
| very long text that looks weird | hello world hello world hello world |
Examples Tanstack Start Spa Content Docs Index (`examples/tanstack-start-spa/content/docs/index.mdx`)
title: Hello World icon: Rocket
Hey there! Fumadocs is the docs framework that also works on Tanstack Start!
Heading
Hello World!
CodeBlock
console.log('Hello World');Table
| Head | Description |
|---|---|
hello |
Hello World |
| very important | Hey |
| Surprisingly | Fumadocs |
| very long text that looks weird | hello world hello world hello world |
Examples Tanstack Start Story Content Docs Index (`examples/tanstack-start-story/content/docs/index.mdx`)
title: Hello World icon: Rocket
import { story } from '@/components/callout.story';
Hey there! Fumadocs is the docs framework that also works on Tanstack Start!
Heading
<story.WithControl />
Hello World!
CodeBlock
console.log('Hello World');Table
| Head | Description |
|---|---|
hello |
Hello World |
| very important | Hey |
| Surprisingly | Fumadocs |
| very long text that looks weird | hello world hello world hello world |
Examples Tanstack Start Content Docs Index (`examples/tanstack-start/content/docs/index.mdx`)
title: Hello World icon: Rocket
Hey there! Fumadocs is the docs framework that also works on Tanstack Start!
Heading
Hello World!
CodeBlock
console.log('Hello World');Table
| Head | Description |
|---|---|
hello |
Hello World |
| very important | Hey |
| Surprisingly | Fumadocs |
| very long text that looks weird | hello world hello world hello world |
Examples Waku I18n Content Docs Index (`examples/waku-i18n/content/docs/index.mdx`)
title: Hello World icon: Rocket
Hey there! Fumadocs is a docs framework that works on Waku!
Heading
Hello World!
CodeBlock
console.log('Hello World');Table
| Head | Description |
|---|---|
hello |
Hello World |
| very important | Hey |
| Surprisingly | Fumadocs |
| very long text that looks weird | hello world hello world hello world |
Examples Waku Content Docs Index (`examples/waku/content/docs/index.mdx`)
title: Hello World icon: Rocket
Hey there! Fumadocs is a docs framework that works on Waku!
Heading
Hello World!
CodeBlock
console.log('Hello World');Table
| Head | Description |
|---|---|
hello |
Hello World |
| very important | Hey |
| Surprisingly | Fumadocs |
| very long text that looks weird | hello world hello world hello world |
Apps Docs Content Docs (Framework) Index (`apps/docs/content/docs/(framework)/index.mdx`)
title: Quick Start icon: Rocket
import { CpuIcon, PanelsTopLeftIcon, DatabaseIcon, TerminalIcon } from 'lucide-react';
Introduction
Fumadocs (Foo-ma docs) is a documentation framework, designed to be fast, flexible,
and composes seamlessly into your React framework. It consists of multiple layers:
<Card icon={
Handles most of the logic, including document search, content source adapters, and Markdown extensions.
The default theme of Fumadocs offers a beautiful look for documentation sites and interactive components.
<Card icon={
The source of your content, can be a CMS or local data layers like Fumadocs MDX (the official content source).
<Card icon={
A command line tool to install UI components and automate things, useful for customizing layouts.
Terminology
Markdown/MDX: Markdown is a markup language for creating formatted text. Fumadocs natively supports Markdown and MDX (superset of Markdown).
Bun: A JavaScript runtime, it will be used in our guides for running scripts. If you prefer Node.js, try unrun instead.
Some basic knowledge of React.js would be useful for further customizations.
Fumapress is a site generator that manages more features on top of Fumadocs.
- Automatic routing,
llms.txt, MCP and more basic features. - Based on Vite (Waku).
It enforces a React framework of its own, and offers a better DX in return.
Automatic Installation
A minimum version of Node.js 22 required.
npm create fumadocs-appIt will ask you the built-in template to use:
- Framework: Next.js, Astro (with React), Waku, React Router, Tanstack Start.
- Content source: Fumadocs MDX.
A new fumadocs app should be initialized, with following features configured by default:
You can follow the [Manual Installation](/docs/manual-installation) guide to get started.Enjoy!
Create your first MDX file in the docs folder.
---
title: Hello World
---
## Yo what's upRun the app in development mode and see http://localhost:3000/docs.
npm run devFAQ
Some common questions you may encounter.
```bash title="pnpm"
pnpm update -i -r --latest
```Routing is handled by your React framework, you need to change the routing structure first.
For example, in Next.js, rename the route (`/docs/*` -> `/info/*`):
<Files>
<Folder name="app/docs" defaultOpen className="opacity-50" disabled>
<File name="layout.tsx" />
</Folder>
<Folder name="app/info" defaultOpen>
<File name="layout.tsx" />
</Folder>
</Files>
Or rename from `/docs/*` to `/*` using a route group:
<Files>
<Folder name="app/(docs)" defaultOpen>
<File name="layout.tsx" />
</Folder>
</Files>
Finally, update the base URL of pages in `source.ts`:import { loader } from 'fumadocs-core/source';
export const source = loader({
baseUrl: '/info', // to the new value [!code highlight]
});<Accordion id='multi-docs' title="How to implement multi-docs?">
We recommend using [Layout Tabs](/docs/ui/layouts/docs#layout-tabs).
</Accordion>For Vite
There's some weird pre-bundling problems with Vite: #3910.
Make sure to exclude Fumadocs from pre-bundling and add it to noExternal:
import { defineConfig } from 'vite';
export default defineConfig({
resolve: {
// add other Fumadocs deps as needed
noExternal: ['fumadocs-core', 'fumadocs-ui', 'fumadocs-openapi', '@fumadocs/base-ui'],
},
});For Next.js
If you have similar Turbopack errors like:
```
JSON file not found: FileSystemPath { ... path: "lib/locales/sources/file.json.json" }
```
It is usually because `require(esm)` is not supported ([#3312](https://github.com/fuma-nama/fumadocs/issues/3312)), please upgrade to Node.js 22+.
On Node.js 23.1, if you encountered this error in production build:
```
Could not find a production build in the '.next' directory.
```
It is a known problem, see [#1021](https://github.com/fuma-nama/fumadocs/issues/1021), you can upgrade your Node.js version to 23.6.0+.Next.js turns dynamic route into static routes when `generateStaticParams` is configured.
Hence, it is as fast as static pages.
You can [enable Static Exports](/docs/deploying/static) on Next.js to get a static build output.Same as managing layouts in Next.js App Router, remove the original MDX file from content directory (`/content/docs`).
This ensures duplicated pages will not cause errors.
Now, You can add the page to another route group, which isn't a descendant of docs layout.
For example, to replace `/docs/test`:
<Files>
<File name="(home)/docs/test/page.tsx" />
<Folder name="docs" defaultOpen>
<File name="layout.tsx" />
<File name="[[...slug]]/page.tsx" />
</Folder>
</Files>
For `/docs`, you need to change the catch-all route to be non-optional:
<Files>
<File name="(home)/docs/page.tsx" />
<Folder name="docs" defaultOpen>
<File name="layout.tsx" />
<File name="[...slug]/page.tsx" />
</Folder>
</Files>Learn More
New to here? Don't worry, we are welcome for your questions.
If you find anything confusing, please give your feedback on Github Discussion!
Writing Content
For authoring docs, make sure to read:
Special Needs
Apps Docs Content Docs Asyncapi Index (`apps/docs/content/docs/asyncapi/index.mdx`)
title: Scalar Example index: true
Apps Docs Content Docs Cli Index (`apps/docs/content/docs/cli/index.mdx`)
title: User Guide
Installation
Initialize a config for CLI:
npx @fumadocs/cliYou can change the output paths of components in the config.
Init
Set up Fumadocs on an existing app, it supports Next.js, React Router, Tanstack Start and Waku.
npx @fumadocs/cli initTo avoid conflicts with your app, the docs pages are added in a dedicated route group (e.g. app/(docs) on Next.js) with their own provider and layout, route handlers like the search API follow the examples. Only a few edits are made to existing files:
- Fumadocs MDX plugin in your framework config.
- Fumadocs UI styles in your global CSS.
suppressHydrationWarningon the<html>element of your root layout, for the theme switch..sourcein.gitignore.
Pass --i18n to set up internationalization: the docs routes get a locale segment (optional for the default locale where the router supports it), a lib/i18n.ts config and, on Next.js, a proxy scoped to the docs routes. Other features follow the i18n config of your project.
It asks before overwriting a file, pass --yes to skip prompts and --no-install to only write dependencies to package.json.
Features
Configure a feature on your project, it installs the needed components & routes, and wires them into your app.
npx @fumadocs/cli feature ai --provider openrouter| Feature | Description |
|---|---|
docs |
Set up Fumadocs, same as init. |
ai |
Ask AI dialog, --provider. |
llms |
LLM routes: llms.txt, llms-full.txt and per-page Markdown. |
mcp |
MCP server to search and read the docs. |
webmcp |
WebMCP tools for AI agents in the browser, experimental. |
og |
Open Graph image generation with Takumi or next/og (Next.js only), --engine. |
search |
3rd party search solution, --provider. |
feedback |
Feedback component on docs pages. |
epub |
Route to export EPUB. |
lint |
Configure ESLint (Next.js only), Biome or Oxlint, --linter. |
Features adapt to your setup:
- Fumadocs MDX with the Macro API or a
source.config.ts, other content sources can't usellms,mcpandepub. - The
baseUrlof your docs,src/directory,next.config.ts, i18n, static export and SPA modes. - React Router with file-based routes: the routes to register are printed instead.
The paths of routes are placed under the constants of lib/shared.ts.
See help for the available options:
npx @fumadocs/cli feature -hComponents
Select and install components.
npx @fumadocs/cli addYou can pass component names directly.
npx @fumadocs/cli add banner filesHow the magic works?
The CLI fetches the latest version of component from the Fumadocs registry (https://fumadocs.dev/registry).
When you install a component, it is guaranteed to be up-to-date.
In addition, it also transforms import paths.
Make sure to use the latest version of CLI
This is highly inspired by Shadcn UI.
Customize
A simple way to customize Fumadocs layouts.
npx @fumadocs/cli customizeIt installs the layout into layouts and points the imports of your route files at it.
Shadcn UI
On projects with a components.json, the CLI follows the aliases of Shadcn UI and:
- installs the
shadcnpreset, so Fumadocs UI adopts your theme colors. - reuses the
button,popoverandcollapsiblein youruidirectory, they share the API of Shadcn UI. - imports
cnfrom yourutilsalias.
Fumadocs' copies are only installed when you don't have them yet.
Tree
Generate files tree for Fumadocs UI Files component, using the tree command from your terminal.
npx @fumadocs/cli tree ./my-dir ./output.tsxYou can output MDX files too:
npx @fumadocs/cli tree ./my-dir ./output.mdxSee help for further details:
npx @fumadocs/cli tree -hExample Output
import { File, Folder, Files } from 'fumadocs-ui/components/files';
export default (
<Files>
<Folder name="app">
<File name="layout.tsx" />
<File name="page.tsx" />
<File name="global.css" />
</Folder>
<Folder name="components">
<File name="button.tsx" />
<File name="tabs.tsx" />
<File name="dialog.tsx" />
</Folder>
<File name="package.json" />
</Files>
);Apps Docs Content Docs Graphql Index (`apps/docs/content/docs/graphql/index.mdx`)
title: Store Example index: true
A store schema documented with Fumadocs GraphQL, its playground sends requests to a mock endpoint.
- Customer has named examples to pick from.
- Order Updated streams live events in the playground.
Apps Docs Content Docs Headless Index (`apps/docs/content/docs/headless/index.mdx`)
title: Introduction icon: SquareBookmark
What is this?
Fumadocs Core offers server-side functions and headless components to build docs on React.js frameworks like Next.js, Waku, and Astro with React islands.
- Search
- Breadcrumb, Sidebar, TOC Components
- Remark/Rehype Plugins
- MCP tools for AI agents to search and read docs
- Additional utilities
It can be used without Fumadocs UI, in other words, it's headless.
For beginners and normal usages, use [Fumadocs UI](/docs/ui).Installation
Install the core package. Individual integrations may require additional dependencies, listed in their guides.
fumadocs-coreFor some components, a framework provider is needed:
import type { ReactNode } from 'react';
import { NextProvider } from 'fumadocs-core/framework/next';
export function RootLayout({ children }: { children: ReactNode }) {
// or if you're using Fumadocs UI, use `<RootProvider />`
return <NextProvider>{children}</NextProvider>;
}import type { ReactNode } from 'react';
import { ReactRouterProvider } from 'fumadocs-core/framework/react-router';
export function Root({ children }: { children: ReactNode }) {
return <ReactRouterProvider>{children}</ReactRouterProvider>;
}import type { ReactNode } from 'react';
import { TanstackProvider } from 'fumadocs-core/framework/tanstack';
export function Root({ children }: { children: ReactNode }) {
return <TanstackProvider>{children}</TanstackProvider>;
}import type { ReactNode } from 'react';
import { WakuProvider } from 'fumadocs-core/framework/waku';
export function Root({ children }: { children: ReactNode }) {
return <WakuProvider>{children}</WakuProvider>;
}import type { ReactNode } from 'react';
import { AstroProvider } from 'fumadocs-core/framework/astro';
export function DocsIsland({
children,
pathname,
params,
}: {
children: ReactNode;
pathname: string;
params: Record<string, string | string[] | undefined>;
}) {
return (
<AstroProvider pathname={pathname} params={params}>
{children}
</AstroProvider>
);
}Explore the APIs and components:
Apps Docs Content Docs Mdx Index (`apps/docs/content/docs/mdx/index.mdx`)
title: Getting Started icon: SquareBookmark
Introduction
Fumadocs MDX is a tool to transform content into type-safe data, similar to Content Collections.
It is not a full CMS but rather a content processing layer for React frameworks, you can use it to handle blog posts and other contents.
What is a Collection?
Collection refers to a collection containing a certain type of files. You can define them with the Macro API in your app:
import { defineDocs } from 'fumadocs-mdx/macro';
import { loader } from 'fumadocs-core/source';
const docs = defineDocs({
dir: 'content/docs',
});
export const source = loader({
baseUrl: '/docs',
source: docs.toFumadocsSource(),
});Available collections:
<Tabs items={["doc", "meta", 'docs']}>
<Tab value='doc'>Compile Markdown & MDX files into a React component, with useful properties like Table of Contents.
import { defineCollections } from 'fumadocs-mdx/macro';
export const test = defineCollections({
type: 'doc',
dir: 'content/docs',
});</Tab>
<Tab value='meta'>Transform YAML/JSON files into an array of data.
import { defineCollections } from 'fumadocs-mdx/macro';
export const test = defineCollections({
type: 'meta',
dir: 'content/docs',
});</Tab>
<Tab value='docs'>Combination of meta and doc collections, which is needed for Fumadocs.
import { defineDocs } from 'fumadocs-mdx/macro';
export const docs = defineDocs({
dir: 'content/docs',
docs: {
// options for `doc` collection
},
meta: {
// options for `meta` collection
},
});</Tab>For example, a doc collection will transform the .md and .mdx files:
You can also define collections in source.config.ts with the Config API, which generates entry files under .source.
Installation
Configure Fumadocs MDX on:
FAQ
Built-in Properties
These properties are exported from MDX files by default.
| Property | Description |
|---|---|
frontmatter |
Frontmatter |
toc |
Table of Contents |
structuredData |
Structured Data, useful for implementing search |
extractedReferences |
For analyzing hrefs references |
Customize Frontmatter
Use the schema option to pass a validation schema to validate frontmatter and define its output properties.
Customize MDX Compiler
Fumadocs MDX uses MDX Compiler to compile MDX files into JavaScript files.
The default preset includes a set of plugins and configurations out-of-the-box.
You can customize it on Global Config or Collection Config.
Apps Docs Content Docs Openapi Index (`apps/docs/content/docs/openapi/index.mdx`)
title: Scalar Example index: true
Apps Docs Content Docs Ui Index (`apps/docs/content/docs/ui/index.mdx`)
title: Overview icon: SquareBookmark
Fumadocs UI offers a beautifully designed theme for documentation sites.
It is bundled with many interactive components & layouts for docs sites, allowing low maintenance cost of code while receiving regular UI improvements.
You can also use Fumadocs CLI to install the components locally and have full control.
Learn More
Examples I18n Content Docs Git Index (`examples/i18n/content/docs/git/index.mdx`)
title: Hello World
Hey there!
Heading
Heading
Heading
Apps Docs Content Docs (Framework) Deploying Index (`apps/docs/content/docs/(framework)/deploying/index.mdx`)
title: Deploying
Overview
Your Fumadocs app is powered by the underlying React framework (e.g. Next.js), you will need to check the relevant docs of your React framework for deployment guides.
If you are looking to host the app as a SPA app (fully static, hostable on CDN), see Static Build.
There's some extra notes on specific combinations:
Next.js + Cloudflare
Use https://opennext.js.org/cloudflare, Fumadocs doesn't work on Edge runtime.
Next.js + Docker
If you want to deploy your Fumadocs app using Docker with Fumadocs MDX configured, make sure to add the source.config.ts file and your next.config.js file to the WORKDIR in the Dockerfile.
The following snippet is taken from the official Next.js Dockerfile Example:
/* Detailed source-code truncated for AI context efficiency. */This ensures Fumadocs MDX can access your configuration file during builds.
Apps Docs Content Docs (Framework) Guides Index (`apps/docs/content/docs/(framework)/guides/index.mdx`)
title: Guides icon: Book
Apps Docs Content Docs (Framework) Internationalization Index (`apps/docs/content/docs/(framework)/internationalization/index.mdx`)
title: Internationalization
npx @fumadocs/cli init --i18n sets up a new docs with i18n, or configure it manually below.
Overview
You'll have to configure i18n routing on your React framework and Fumadocs.
Fumadocs is not a full-powered i18n library, it's up to you when internationalizing the rest of your app.
You can also use other libraries with Fumadocs like next-intl on Next.js.
Apps Docs Content Docs (Framework) Manual Installation Index (`apps/docs/content/docs/(framework)/manual-installation/index.mdx`)
title: Manual Installation icon: Wrench
You can also let Fumadocs CLI set it up for you:
npx @fumadocs/cli initApps Docs Content Docs (Framework) Markdown Index (`apps/docs/content/docs/(framework)/markdown/index.mdx`)
title: Markdown
import { Building, HomeIcon, Rocket } from 'lucide-react';
Introduction
Fumadocs provides many useful extensions to MDX, a markup language. Here is a brief introduction to the default MDX syntax of Fumadocs.
MDX is not the only supported format of Fumadocs. In fact, you can use any renderer such as headless CMS.
See Custom Content Source for details.
MDX
We recommend MDX, a superset of Markdown with JSX syntax.
It allows you to import components, and use them in the document, or even writing JavaScript.
See:
- MDX Syntax.
- GFM (GitHub Flavored Markdown) is also supported, see GFM Specification.
import { Component } from './component';
<Component name="Hello" />
## Heading
### Heading
#### Heading
Hello World, **Bold**, _Italic_, ~~Hidden~~
1. First
2. Second
3. Third
- Item 1
- Item 2
> Quote here
| Table | Description |
| ----- | ----------- |
| Hello | World |Frontmatter
Fumadocs support YAML-based frontmatter by default, title represents the page title (h1) in Fumadocs UI.
---
title: This is a document
---
...For this reason, h1 title (# Heading) is usually unused when writing Markdown/MDX unless you have custom logic for page title rendering.
You can use the schema option to add frontmatter properties.
Auto Links
Internal links use the <Link /> component of your React framework (e.g. next/link) to allow prefetching and avoid hard-reload.
External links will get the default rel="noreferrer noopener" target="_blank" attributes for security.
[My Link](https://github.github.com/gfm)
This also works: https://github.github.com/gfm.Cards
Useful for adding links.
import { HomeIcon } from 'lucide-react';
<Cards>
<Card
href="https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating"
title="Fetching, Caching, and Revalidating"
>
Learn more about caching in Next.js
</Card>
<Card title="href is optional">Learn more about `fetch` in Next.js.</Card>
<Card icon={<HomeIcon />} href="/" title="Home">
You can include icons too.
</Card>
</Cards>"Further Reading" Section
You can do something like:
import { getPageTreePeers } from 'fumadocs-core/page-tree';
import { source } from '@/lib/source';
<Cards>
{getPageTreePeers(source.getPageTree(), '/docs/my-page').map((peer) => (
<Card key={peer.url} title={peer.name} href={peer.url}>
{peer.description}
</Card>
))}
</Cards>;This will show the other pages in the same folder as cards.
Callouts
Useful for adding tips/warnings, it is included by default. You can specify the type of callout:
info(default)warn/warningerrorsuccessidea
<Callout>Hello World</Callout>
<Callout title="Title" type="warn">
Hello World
</Callout>
<Callout title="Title" type="error">
Hello World
</Callout>
<Callout title="Title" type="idea">
Hello World
</Callout>Headings
An anchor is automatically applied to each heading, it sanitizes invalid characters like spaces. (e.g. Hello World to hello-world)
# Hello `World`TOC Settings
The table of contents (TOC) will be generated based on headings, you can also customize the effects of headings:
# Heading [!toc]
This heading will be hidden from TOC.
# Another Heading [toc]
This heading will **only** be visible in TOC, you can use it to add additional TOC items.
Like headings rendered in a React component:
<MyComp />Custom Anchor
You can add [#slug] to customize heading anchors.
# heading [#my-heading-id]You can also chain it with TOC settings like:
# heading [toc] [#my-heading-id]To link people to a specific heading, add the heading id to hash fragment: /page#my-heading-id.
Codeblock
Syntax Highlighting is supported by default using Rehype Code.
```js
console.log('Hello World');
```
```js title="My Title"
console.log('Hello World');
```Line Numbers
Show line numbers, it also works with Twoslash and other transformers.
```ts twoslash lineNumbers
const a = 'Hello World';
// ^?
console.log(a); // [!code highlight]
```const a = 'Hello World';
// ^?
console.log(a); // [!code highlight]You can set the initial value of line numbers.
```js lineNumbers=4
function main() {
console.log('starts from 4');
return 0;
}
```function main() {
console.log('starts from 4');
return 0;
}Shiki Transformers
We support some of the Shiki Transformers, allowing you to highlight/style specific lines.
```tsx
// highlight a line
Hello World // [\!code highlight]
// highlight a word
// [\!code word:Fumadocs]
Fumadocs
// diff styles
console.log('hewwo'); // [\!code --]
console.log('hello'); // [\!code ++]
// focus
return new ResizeObserver(() => {}) // [\!code focus]
```// highlight a line
<div>Hello World</div> // [!code highlight]
// highlight a word
// [!code word:Fumadocs]
<div>Fumadocs</div>
// diff styles:
console.log('hewwo'); // [!code --]
console.log('hello'); // [!code ++]
// focus
return new ResizeObserver(() => {}) // [!code focus]Tab Groups
```ts tab="Tab 1"
console.log('A');
```
```ts tab="Tab 2"
console.log('B');
```console.log('A');console.log('B');To add a persist ID, define tab-group at the first codeblock:
```ts tab="Tab 1" tab-group="my-custom-group"
console.log('A');
```
```ts tab="Tab 2"
console.log('B');
```console.log('A');console.log('B');Another group:
console.log('A');console.log('B');MDX in Tab Groups
While disabled by default, you use MDX in tab values by configuring the remark plugin:
import { defineConfig } from 'fumadocs-mdx/config';
export default defineConfig({
mdxOptions: {
remarkCodeTabOptions: {
parseMdx: true, // [!code ++]
},
},
});import { compile } from '@mdx-js/mdx';
import { remarkCodeTab } from 'fumadocs-core/mdx-plugins';
await compile('...', {
remarkPlugins: [
[
remarkCodeTab,
{
parseMdx: true, // [!code ++]
},
],
],
});```ts tab=" Tab 1"
console.log('A');
```
```ts tab="<Rocket /> Tab 2"
console.log('B');
``` tab 1"">console.log('A'); tab 2"">console.log('B');Include
This is only available on Fumadocs MDX.
Reference another file (can also be a Markdown/MDX document).
Specify the target file path in `` tag (relative to the MDX file itself).
<include>./another.mdx</include>NPM Commands
Useful for generating commands for installing packages with different package managers.
```npm
npm i next -D
```npm i next -DSee remark-npm for details.
Steps
Useful for denoting steps for guides, see remark-steps for enable & configure.
### Installation [step]
### Write Code [step]
### Deploy [step]Installation [step]
Write Code [step]
Deploy [step]
Other Components
You can configure & use the built-in components in your MDX document, such as Tabs, Accordions and Zoomable Image.
Additional Features
You may be interested:
Apps Docs Content Docs (Framework) Search Index (`apps/docs/content/docs/(framework)/search/index.mdx`)
title: Search
Search UI
See Search UI to learn how to customize the UI of document search.
Search Client
You can choose & configure the search client according to your search engine, it defaults to ZBSearch.
Search Hooks
Search clients come with React hooks, like useFetchSearch() and useAlgoliaSearch(), returning the props of <SearchDialog />:
import { useFetchSearch } from 'fumadocs-core/search/client';
const search = useFetchSearch();
<SearchDialog {...search} {...props} />;Along with search and onSearchChange for the search input, they return:
data: the last successful search, kept while searching again and after an error.items: its results, with their content also parsed as hast ashastContent. The rows of a table are grouped into atableitem.query: the query it searched for.
error: set when the last search failed.isLoading: whether a search forsearchis pending.
For other search engines, pass a memoized search function to experimental_useSearch(), it searches again when the function changes.
'use client';
import { useCallback } from 'react';
import { experimental_useSearch } from 'fumadocs-core/search/client';
import type { SortedResult } from 'fumadocs-core/search';
export function useMySearch(locale: string) {
const search = useCallback(
async (query: string): Promise<SortedResult[]> => {
const res = await fetch(`/api/my-search?${new URLSearchParams({ query, locale })}`);
return res.json();
},
[locale],
);
return experimental_useSearch(search);
}Community Integrations
A list of integrations maintained by community.
Apps Docs Content Docs Headless Content Collections Index (`apps/docs/content/docs/headless/content-collections/index.mdx`)
title: Content Collections
Content Collections is a library that transforms your content into type-safe data collections.
Setup
Follow their official guide to setup Content Collections for your React framework.
Make sure you have MDX configured:
npm i @content-collections/mdxTo integrate with Fumadocs, add the following to your content-collections.ts.
../../examples/content-collections/content-collections.ts
And pass it to loader().
../../examples/content-collections/lib/source.ts
Done! You can access the pages and generated page tree from Source API.
import { getPage } from '@/lib/source';
const page = getPage(slugs);
// MDX output
page?.data.body;
// Table of contents
page?.data.toc;
// Structured Data, for Search API
page?.data.structuredData;MDX Options
You can customize MDX options in the transformMDX function.
import { defineCollection } from '@content-collections/core';
import { transformMDX } from '@fumadocs/content-collections/configuration';
const docs = defineCollection({
transform: (document, context) =>
transformMDX(document, context, {
// options here
}),
});Import Components
To use components from other packages like Fumadocs UI, pass them to your <MDXContent /> component.
import { MDXContent } from '@content-collections/mdx/react';
import { getMDXComponents } from '@/components/mdx';
return <MDXContent code={page.data.body} components={getMDXComponents()} />;You can also import them in MDX Files, but it is not recommended.
To support importing a package from node modules, Fumadocs added a default value to the `cwd` option of MDX Bundler.
It works good, but we still **do not** recommend importing components in MDX files.
Reasons:
- It requires esbuild to bundle these components, while it should be done by the framework's bundler (e.g. Vite or Turbopack)
- You can refactor the import path of components without changing your MDX files.
- With Remote Sources, it doesn't make sense to add an import in MDX files.Apps Docs Content Docs Headless Mdx Index (`apps/docs/content/docs/headless/mdx/index.mdx`)
title: MDX Plugins index: true
Apps Docs Content Docs Headless Search Index (`apps/docs/content/docs/headless/search/index.mdx`)
title: Search icon: Search index: true
Apps Docs Content Docs Headless Source Api Index (`apps/docs/content/docs/headless/source-api/index.mdx`)
title: Loader API
What it does?
loader() provides an interface for Fumadocs to integrate with different content sources.
- Generate page slugs and page tree.
- Assign URL to each page.
- Output useful utilities to interact with content.
loader()is a server-side API, not a build-time magic or browser compatible API.- It uses in-memory storage, the files are passed from your content sources.
Usage
You can use it with content sources like Fumadocs MDX, see source for details.
import { loader } from 'fumadocs-core/source';
import { docs } from 'collections/server';
export const source = loader({
source: docs.toFumadocsSource(),
baseUrl: '/docs',
});URL
You can override the base URL, or specify a function to generate URL for each page.
import { loader } from 'fumadocs-core/source';
loader({
baseUrl: '/docs',
// or you can customize it with function
url(slugs, locale) {
if (locale) return '/' + [locale, 'docs', ...slugs].join('/');
return '/' + ['docs', ...slugs].join('/');
},
});Slugs
Define a custom function to generate slugs from page files.
import { loader } from 'fumadocs-core/source';
loader({
slugs(file, next) {
console.log(file.path, file.data);
// you can build on the default slugs with `next()`
if (file.path.startsWith('blog/')) return ['blog', ...next()];
// or return `undefined` to fallback to default value
return ['my', 'slug'];
},
});Icons
Load the icon property specified by page and meta files.
import { loader } from 'fumadocs-core/source';
import { icons } from 'lucide-react';
import { createElement } from 'react';
loader({
icon(icon) {
if (!icon) {
// You may set a default icon
return;
}
if (icon in icons) return createElement(icons[icon as keyof typeof icons]);
},
});I18n
Pass the i18n config to loader.
import { i18n } from '@/lib/i18n';
import { loader } from 'fumadocs-core/source';
export const source = loader({
i18n, // [!code highlight]
});With i18n enabled, loader will generate a page tree for every locale.
If translations are missing for a page, it fallbacks to fallbackLanguage.
Output
It outputs a content loader instance.
Get Page
Get page with slugs.
import { source } from '@/lib/source';
source.getPage(['slug', 'of', 'page']);
// with i18n
source.getPage(['slug', 'of', 'page'], 'locale');Get Pages
Get a list of page available for locale.
import { source } from '@/lib/source';
// from any locale
source.getPages();
// for a specific locale
source.getPages('locale');Page Tree
import { source } from '@/lib/source';
// without i18n
source.getPageTree();
// with i18n
source.getPageTree('locale');Get from Node
The page tree nodes contain references to their original file path.
You can find their original page or meta file from the tree nodes.
import { source } from '@/lib/source';
source.getNodePage(pageNode);
source.getNodeMeta(folderNode);Params
A function to generate output for Next.js generateStaticParams.
The generated parameter names will be slug: string[] and lang: string (i18n only).
import { source } from '@/lib/source';
export function generateStaticParams() {
return source.generateParams();
}Language Entries
Get available languages and its pages.
import { source } from '@/lib/source';
// language -> pages
const entries = source.getLanguages();Client API
Loader API is best when used in RSC environments, which allows passing JSX nodes across the server-client boundary.
For non-RSC environments, it provides a tiny serialization layer.
import { source } from '@/lib/source';
// where you pass loader data
async function loader() {
const pageTree = source.getPageTree();
return {
pageTree: await source.serializePageTree(pageTree),
};
}import { useFumadocsLoader } from 'fumadocs-core/source/client';
function MyComponent() {
// `useLoaderData()` receives loader data (provided by your React framework)
const { pageTree, ...data } = useFumadocsLoader(useLoaderData());
// now render it (e.g. via Fumadocs UI)
return <DocsLayout tree={pageTree}>...</DocsLayout>;
}Dynamic Loader
dynamicLoader() provides extra revalidation functionality to content sources, you should use it with a dynamic source.
import { dynamicLoader } from 'fumadocs-core/source';
import { createMySource } from './my-source';
const source = dynamicLoader(createMySource(), {
baseUrl: '/docs',
});
export async function getSource() {
return source.get();
}When you call await source.get(), it returns a normal content loader object:
const docs = await getSource();
const page = docs.getPage(['...']);Revalidation
Dynamic sources are cached by the loader.
If your source changes over time, call invalidate() to clear the cache, or revalidate() to update the cache:
source.invalidate();
await source.revalidate();For multi-source setups, you can invalidate/revalidate a specific source:
source.invalidate('docs');
await source.revalidate('docs');Apps Docs Content Docs Headless Utils Index (`apps/docs/content/docs/headless/utils/index.mdx`)
title: Utilities index: true
Apps Docs Content Docs Mdx Entry Index (`apps/docs/content/docs/mdx/entry/index.mdx`)
title: Accessing Collections icon: FileUp
Overview
After defining collections in your config file, collection entries are compiled into JavaScript files that your app can access.
While the compilation will be done by Fumadocs MDX's bundler/runtime plugins, it also generates entry files to access the collection outputs.
If you prefer defining collections in app modules instead, see the Macro API.
Entry Files
Collection outputs will be generated under the output directory, you can access them from entry files.
See below for a list of available ways to access outputs.
Apps Docs Content Docs Ui Components Index (`apps/docs/content/docs/ui/components/index.mdx`)
title: Components
Overview
Additional components that you can use:
MDX Components
The default MDX components include Cards, Callouts, Code Blocks and Headings.
import defaultMdxComponents from 'fumadocs-ui/mdx';Relative Link
To support links with relative file path in href, override the default a component with:
import { createRelativeLink } from 'fumadocs-ui/mdx';
import { source } from '@/lib/source';
import { getMDXComponents } from '@/components/mdx';
const page = source.getPage(['...']);
return (
<MdxContent
components={getMDXComponents({
// override the `a` tag
a: createRelativeLink(source, page),
})}
/>
);[My Link](./file.mdx)Example: ../../(integrations)/feedback.mdx
Apps Docs Content Docs Ui Layouts Index (`apps/docs/content/docs/ui/layouts/index.mdx`)
title: Layouts
Overview
Fumadocs UI provides essential layouts to display content.
Configurations
Each layout supports a shared set of options.
It is recommended to store these options into a file, and pass them to the layouts.
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
export function baseOptions(): BaseLayoutProps {
return {
nav: {
title: 'My App',
},
};
}import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import { baseOptions } from '@/lib/layout.shared';
import { source } from '@/lib/source';
import type { ReactNode } from 'react';
export default function Layout({ children }: { children: ReactNode }) {
return (
<DocsLayout
// [!code highlight]
{...baseOptions()}
tree={source.getPageTree()}
>
{children}
</DocsLayout>
);
}import { HomeLayout } from 'fumadocs-ui/layouts/home';
import { baseOptions } from '@/lib/layout.shared';
import type { ReactNode } from 'react';
export default function Layout({ children }: { children: ReactNode }) {
return (
<HomeLayout
// [!code highlight]
{...baseOptions()}
>
{children}
</HomeLayout>
);
}See detailed docs for links and nav options.
Packages Create App Template Astro Content Docs Index (`packages/create-app/template/astro/content/docs/index.mdx`)
title: Hello World
Welcome to the docs! You can start writing documents in /content/docs.
console.log('Hello World');What's next?
Apps Docs Content Docs (Framework) Integrations Asyncapi Index (`apps/docs/content/docs/(framework)/integrations/asyncapi/index.mdx`)
title: AsyncAPI
Setup
Install the required packages.
npm i @fumadocs/asyncapi shikiGenerate Styles
Add the following line:
@import 'tailwindcss';
@import 'fumadocs-ui/css/neutral.css';
@import 'fumadocs-ui/css/preset.css';
/* [!code ++] */
@import '@fumadocs/asyncapi/css/preset.css';Configure Plugin
Create the AsyncAPI server instance & <AsyncAPIPage /> component.
import { createAsyncAPI } from '@fumadocs/asyncapi/server';
// note: this is a server-side API
export const asyncapi = createAsyncAPI({
// the AsyncAPI schema, you can also give it an external URL.
input: ['./asyncapi.yaml'],
});'use client';
import { createAsyncAPIPage } from '@fumadocs/asyncapi/ui';
export const AsyncAPIPage = createAsyncAPIPage();import { asyncapiPlugin } from '@fumadocs/asyncapi/server';
import { loader } from 'fumadocs-core/source';
export const source = loader({
// [!code ++] optional: adds the direction of messages to each page item in page tree
plugins: [asyncapiPlugin()],
});See createAsyncAPI() & createAsyncAPIPage() for available options.
Generate Pages
<Tabs items={["MDX Files", "Virtual Files"]}>
You can generate MDX files directly from your AsyncAPI schema.
Create a script:
import { generateFiles } from '@fumadocs/asyncapi';
import { asyncapi } from '@/lib/asyncapi';
void generateFiles({
input: asyncapi,
output: './content/docs',
// we recommend to enable it
// make sure your operation description doesn't break MDX syntax.
includeDescription: true,
});Generate docs with the script:
bun ./scripts/generate-docs.tsAdd the AsyncAPIPage component to your MDX components. Generated files use the <AsyncAPIPage /> tag.
import { source } from '@/lib/source';
import { asyncapi } from '@/lib/asyncapi';
import { AsyncAPIPage } from '@/components/api-page';
import { getMDXComponents } from '@/components/mdx';
// e.g. in your page renderer
export default function Page({ slug }) {
const page = source.getPage(slug);
const MdxContent = page.data.body;
return (
<MdxContent
components={getMDXComponents({
// [!code ++:3] add the MDX component
AsyncAPIPage: async (props) => (
<AsyncAPIPage {...await asyncapi.preloadAsyncAPIPage(page)} {...props} />
),
})}
/>
);
}import { source } from '@/lib/source';
import { asyncapi } from '@/lib/asyncapi';
import { AsyncAPIPage } from '@/components/api-page';
import { getMDXComponents } from '@/components/mdx';
// e.g. where you return server payload to client
export function loader({ slug }) {
const page = source.getPage(slug);
return {
// [!code ++]
asyncapiData: await asyncapi.preloadAsyncAPIPage(page),
// ...
};
}
// e.g. in your page renderer
export default function Page() {
// consume loader data
const { asyncapiData } = useLoaderData();
return (
<MdxContent
components={getMDXComponents({
// [!code ++] add the MDX component
AsyncAPIPage: (props) => <AsyncAPIPage {...asyncapiData} {...props} />,
})}
/>
);
}You can also use it without generating real files by integrating into Loader API.
import { loader } from 'fumadocs-core/source';
import { docs } from 'collections/server';
import { asyncapi } from '@/lib/asyncapi';
export const source = loader(
// [!code ++:6]
{
docs: docs.toFumadocsSource(),
asyncapi: await asyncapi.staticSource({
baseDir: 'asyncapi',
}),
},
{
baseUrl: '/docs',
plugins: [asyncapi.loaderPlugin()],
// ...
},
);staticSource() is a server-side API that generates pages directly to your loader(), hence it allows dynamic content generation, such as re-generating page tree as schema changes.
It will change the type of your pages, make sure to update all references to your source.
For example, where you return text for LLM, getText() returns the operations of page in Markdown, with their payloads as TypeScript types:
import { llms } from 'fumadocs-core/source';
import { source } from '@/lib/source';
export const docsLlms = llms(source, {
renderPage: async (page) => {
// [!code ++:3]
if (page.type === 'asyncapi') {
return `# ${page.data.title}\n\n${await page.data.getText('processed')}`;
}
// your original flow below...
},
});And update your page renderer:
<Tabs items={["Server Components", "Without Server Components"]}>
import { AsyncAPIPage } from '@/components/api-page';
export default function Page({ slug }) {
const page = source.getPage(slug);
// for AsyncAPI pages
if (page.type === 'asyncapi') {
return (
<DocsPage full>
<h1 className="text-[1.75em] font-semibold">{page.data.title}</h1>
<DocsBody>
<AsyncAPIPage {...page.data.getAsyncAPIPageProps()} />
</DocsBody>
</DocsPage>
);
}
// your original flow below...
}Pass a client payload from server, then render the page using the <AsyncAPIPage /> component you created above.
For example, in Tanstack Start:
/* Detailed source-code truncated for AI context efficiency. */You can see the full AsyncAPI example.
Run a type check to verify before continuing, e.g.
npm run types:checkFeatures
The official AsyncAPI integration supports AsyncAPI 3:
- The direction of messages for readers, the server and channel address of operations
- Messages with their payload and header schemas, including Avro, Protobuf and RAML schemas
- Message examples, from the document or generated from schemas
- Request and reply, correlation IDs
- Security schemes, including OAuth 2.0 flows and SASL mechanisms
- Protocol details from bindings, for the selected server
- A playground for WebSocket, Server-Sent Events and custom transports, with text and binary messages
- Code samples in JavaScript and Python, for WebSocket, MQTT, Kafka, AMQP and NATS
- TypeScript definitions of payloads and headers
- Markdown of pages for LLMs
- Traits
AsyncAPI 2 documents can be converted with npx @asyncapi/cli convert.
Demo
See the AsyncAPI example.
Apps Docs Content Docs (Framework) Integrations Content Index (`apps/docs/content/docs/(framework)/integrations/content/index.mdx`)
title: Content Source
Introduction
Fumadocs is very flexible. You can integrate with any content source, even without an official adapter.
Examples
You can see examples to use Fumadocs with a CMS, which allows a nice experience on publishing content, and real-time update without re-building the app.
Not enough? Build your own content source.
Apps Docs Content Docs (Framework) Integrations Graphql Index (`apps/docs/content/docs/(framework)/integrations/graphql/index.mdx`)
title: GraphQL
Setup
Install the required packages.
npm i @fumadocs/graphql graphqlGenerate Styles
Add the following line:
@import 'tailwindcss';
@import 'fumadocs-ui/css/neutral.css';
@import 'fumadocs-ui/css/preset.css';
/* [!code ++] */
@import '@fumadocs/graphql/css/preset.css';Configure Plugin
Create the GraphQL server instance & <GraphQLPage /> component.
import { createGraphQL } from '@fumadocs/graphql/server';
// note: this is a server-side API
export const graphql = createGraphQL({
// the GraphQL schema, it accepts:
// SDL files/URLs (including `extend type`), SDL text,
// introspection results, and `GraphQLSchema` instances.
input: ['./schema.graphql'],
});'use client';
import { createGraphQLPage } from '@fumadocs/graphql/ui';
export const GraphQLPage = createGraphQLPage({
playground: {
// enable the interactive playground on operation pages (optional)
url: 'https://api.example.com/graphql',
},
});See createGraphQL() & createGraphQLPage() for available options.
Generate Pages
Integrate into Loader API to generate pages, without generating real files.
import { loader } from 'fumadocs-core/source';
import { docs } from 'collections/server';
import { graphql } from '@/lib/graphql';
export const source = loader(
// [!code ++:6]
{
docs: docs.toFumadocsSource(),
graphql: await graphql.staticSource({
baseDir: 'graphql',
}),
},
{
baseUrl: '/docs',
// [!code ++] optional: adds a query/mutation/subscription badge to each page item in page tree
plugins: [graphql.loaderPlugin()],
// ...
},
);It generates a page per operation by default, grouped into folders by their kind (e.g. queries/, mutations/). Operation pages show the types they use in place, pages of named types are opt-in.
It will change the type of your pages, make sure to update all references to your source, and update your page renderer:
import { GraphQLPage } from '@/components/api-page';
export default function Page({ slug }) {
const page = source.getPage(slug);
// for GraphQL pages
if (page.type === 'graphql') {
return (
<DocsPage toc={page.data.toc} full>
<DocsTitle>{page.data.title}</DocsTitle>
<DocsDescription>{page.data.description}</DocsDescription>
<DocsBody>
<GraphQLPage {...page.data.getGraphQLPageProps()} />
</DocsBody>
</DocsPage>
);
}
// your original flow below...
}Run a type check to verify before continuing, e.g.
npm run types:checkFeatures
The official GraphQL integration supports:
- A page per operation (query/mutation/subscription), with the types it uses explored in place
- Arguments, fields, deprecations, default values and
@oneOfinput objects - Directives applied in the schema, with their definitions
- Example requests and responses, generated or from your own operations
- Code samples in cURL, JavaScript, Go, Python, Java, C# and Rust
- A playground with a variables form, it runs subscriptions over WebSocket or SSE too
- Markdown of pages for LLMs
- Optional pages for named types, with their usages across the schema
Demo
See the GraphQL example.
Apps Docs Content Docs (Framework) Integrations Og Index (`apps/docs/content/docs/(framework)/integrations/og/index.mdx`)
title: OG Image Generation index: true
You can configure Open Graph image metadata, which defines the preview image of docs pages in website/social media, such as:
Apps Docs Content Docs (Framework) Integrations Openapi Index (`apps/docs/content/docs/(framework)/integrations/openapi/index.mdx`)
title: OpenAPI
Setup
Install the required packages.
npm i fumadocs-openapi shikiGenerate Styles
Add the following line:
@import 'tailwindcss';
@import 'fumadocs-ui/css/neutral.css';
@import 'fumadocs-ui/css/preset.css';
/* [!code ++] */
@import 'fumadocs-openapi/css/preset.css';Configure Plugin
Create the OpenAPI server instance & <OpenAPIPage /> component.
import { createOpenAPI } from 'fumadocs-openapi/server';
// note: this is a server-side API
export const openapi = createOpenAPI({
// the OpenAPI schema, you can also give it an external URL.
input: ['./openapi.json'],
});'use client';
import { createOpenAPIPage } from 'fumadocs-openapi/ui';
export const OpenAPIPage = createOpenAPIPage();import { openapiPlugin } from 'fumadocs-openapi/server';
import { loader } from 'fumadocs-core/source';
export const source = loader({
// [!code ++] optional: adds a badge to each page item in page tree
plugins: [openapiPlugin()],
});See createOpenAPI() & createOpenAPIPage() for available options.
Generate Pages
<Tabs items={["MDX Files", "Virtual Files"]}>
You can generate MDX files directly from your OpenAPI schema.
Create a script:
import { generateFiles } from 'fumadocs-openapi';
import { openapi } from '@/lib/openapi';
void generateFiles({
input: openapi,
output: './content/docs',
// we recommend to enable it
// make sure your endpoint description doesn't break MDX syntax.
includeDescription: true,
});Generate docs with the script:
bun ./scripts/generate-docs.tsAdd the OpenAPIPage component to your MDX components.
import { source } from '@/lib/source';
import { openapi } from '@/lib/openapi';
import { OpenAPIPage } from '@/components/api-page';
import { getMDXComponents } from '@/components/mdx';
// e.g. in your page renderer
export default function Page({ slug }) {
const page = source.getPage(slug);
const MdxContent = page.data.body;
return (
<MdxContent
components={getMDXComponents({
// [!code ++:3] add the MDX component
OpenAPIPage: async (props) => (
<OpenAPIPage {...await openapi.preloadOpenAPIPage(page)} {...props} />
),
})}
/>
);
}import { source } from '@/lib/source';
import { openapi } from '@/lib/openapi';
import { OpenAPIPage } from '@/components/api-page';
import { getMDXComponents } from '@/components/mdx';
// e.g. where you return server payload to client
export function loader({ slug }) {
const page = source.getPage(slug);
return {
// [!code ++]
openapiData: await openapi.preloadOpenAPIPage(page),
// ...
};
}
// e.g. in your page renderer
export default function Page() {
// consume loader data
const { openapiData } = useLoaderData();
return (
<MdxContent
components={getMDXComponents({
// [!code ++] add the MDX component
OpenAPIPage: (props) => <OpenAPIPage {...openapiData} {...props} />,
})}
/>
);
}You can also use it without generating real files by integrating into Loader API.
import { loader } from 'fumadocs-core/source';
import { docs } from 'collections/server';
import { openapi } from '@/lib/openapi';
export const source = loader(
// [!code ++:6]
{
docs: docs.toFumadocsSource(),
openapi: await openapi.staticSource({
baseDir: 'openapi',
}),
},
{
baseUrl: '/docs',
plugins: [openapi.loaderPlugin()],
// ...
},
);staticSource() is a server-side API that generates pages directly to your loader(), hence it allows dynamic content generation, such as re-generating page tree as schema changes.
It will change the type of your pages, make sure to update all references to your source.
For example, where you return text for LLM:
import { llms } from 'fumadocs-core/source';
import { source } from '@/lib/source';
export const docsLlms = llms(source, {
renderPage: (page) => {
// [!code ++:4]
if (page.type === 'openapi') {
// e.g. return the stringified OpenAPI schema
return JSON.stringify(page.data.getSchema().bundled, null, 2);
}
// your original flow below...
},
});And update your page renderer:
<Tabs items={["Server Components", "Without Server Components"]}>
import { OpenAPIPage } from '@/components/api-page';
export default function Page({ slug }) {
const page = source.getPage(slug);
// for OpenAPI pages
if (page.type === 'openapi') {
return (
<DocsPage full>
<h1 className="text-[1.75em] font-semibold">{page.data.title}</h1>
<DocsBody>
<OpenAPIPage {...page.data.getOpenAPIPageProps()} />
</DocsBody>
</DocsPage>
);
}
// your original flow below...
}Pass a client payload from server, then render the page using the <OpenAPIPage /> component you created above.
For example, in Tanstack Start. Only import source on the server, staticSource() reads files: define Macro API collections in another module, like lib/collections.ts.
/* Detailed source-code truncated for AI context efficiency. */You can see the full Tanstack Start example.
Run a type check to verify before continuing, e.g.
npm run types:checkFeatures
The official OpenAPI integration supports:
- Basic API endpoint information
- Interactive API playground
- Example code to send request (in different programming languages)
- Response samples and TypeScript definitions
- Request parameters and body generated from schemas
Demo
Apps Docs Content Docs (Framework) Integrations Story Index (`apps/docs/content/docs/(framework)/integrations/story/index.mdx`)
title: Story
import { story } from './client.story';
<story.WithControl />
Introduction
You can use Fumadocs Story to display & document components.
It is a simple, docs-focused alternative of Storybook, it is mainly designed for component libraries.
If you are new to the concept of Story, this is a short explanation from Storybook docs:
A story captures the rendered state of a UI component. It's an object with annotations that
describe the component's behavior and appearance given a set of arguments.
Fumadocs Story is not a replacement for Storybook, we still recommend Storybook for proper UI testing.
Installation
Follow the installation guide according to your setup:
Configurations
Variants
You can provide multiple variants of the component with args:
import { defineStory } from '@/lib/story';
import { Callout } from '@/components/callout';
export const story = defineStory({
Component: Callout,
args: [
{
variant: 'Default',
initial: {
title: 'This is a Callout',
},
},
{
variant: 'Warning',
initial: {
title: 'This is a Callout',
},
// fixed values for props
fixed: {
type: 'warning',
},
},
],
});`controls`
Controls are generated from the component props, you can specify the control nodes instead:
import { defineStory } from '@/lib/story';
import { GraphView } from '@/components/graph-view';
export const story = defineStory({
Component: GraphView,
args: {
controls: {
node: {
type: 'object',
properties: [],
},
},
},
});`frame`
Components filling the viewport, like layouts, can be rendered in an iframe.
The story panel scales the frame down to fit, with a button to open it in fullscreen.
import { defineStory } from '@/lib/story';
import { MyLayout } from './my-layout.lazy';
import { Window } from './window';
export const story = defineStory({
Component: MyLayout,
frame: {
src: '/story/my-layout',
// viewport size, optional
width: 1280,
height: 800,
// wraps the frame, e.g. in a window, optional
Container: Window,
},
});Create a page at src to render the component, it receives the arguments from the story panel:
import { story } from '@/components/my-layout.story';
export default function Page() {
return <story.Frame />;
}Pages with the story would load the component too, pass a lazy one (e.g. next/dynamic or React.lazy) so only the frame loads it:
'use client';
import dynamic from 'next/dynamic';
export const MyLayout = dynamic(() => import('./my-layout').then((mod) => mod.MyLayout));- The page must be on the same origin as your docs.
- Arguments are sent with
postMessage(), they must be serializable (no functions or JSX). Containermust be a client component.
Internationalization
Assuming you have configured Internationalization at UI level:
import { defineI18n } from 'fumadocs-core/i18n';
import { uiTranslations } from 'fumadocs-ui/i18n';
import { storyTranslations } from '@fumadocs/story/i18n';
const i18n = defineI18n({
languages: ['en', 'cn'],
defaultLanguage: 'en',
});
export const translations = i18n
.translations()
.extend(uiTranslations())
// [!code ++]
.extend(storyTranslations())
.add({
cn: {
displayName: 'Chinese',
// [!code ++]
'Add Item(story arguments form)': '新增項目',
},
});See Translations for more details.
Apps Docs Content Docs Mdx (Integrations) Loader Index (`apps/docs/content/docs/mdx/(integrations)/loader/index.mdx`)
title: Runtime Loader
Introduction
By default, Fumadocs MDX requires a bundler to work.
When you are accessing your content without a bundler, such as:
- A script executed by Node.js or Bun.
- Any unbundled environment.
You will need a runtime loader. See below for available loader integrations.
Apps Docs Components Agent Instructions (`apps/docs/components/agent-instructions.md`)
Set up a Fumadocs site
Instructions for AI coding agents, follow them in order. Fumadocs docs: https://fumadocs.dev/docs, append .mdx to any docs page URL to get it as Markdown, https://fumadocs.dev/llms.txt lists all pages.
1. Keep the docs site separate
Create the docs as its own app: a new directory or repository, or a new workspace package (e.g. apps/docs) in a monorepo.
Don't add Fumadocs to an existing marketing or product app unless the user explicitly asks for it: Fumadocs UI ships global styles and its own layouts, mixing them into an unrelated app leads to style conflicts, coupled deployments and harder upgrades. If the user does want it inside an existing app, skip step 2 and run npx @fumadocs/cli@latest init --yes in the app (adds the docs in a dedicated route group, supports Next.js, Waku, React Router and Tanstack Start), or follow the manual installation guide of the framework:
- Next.js: https://fumadocs.dev/docs/manual-installation/next.mdx (Recommended)
- Waku: https://fumadocs.dev/docs/manual-installation/waku.mdx (Recommended if user prefer Vite)
- React Router: https://fumadocs.dev/docs/manual-installation/react-router.mdx
- Tanstack Start: https://fumadocs.dev/docs/manual-installation/tanstack-start.mdx
- Astro: https://fumadocs.dev/docs/manual-installation/astro.mdx (Not recommended, some features and syntax are not supported due to framework limitations)
2. Scaffold with create-fumadocs-app
Use the official scaffolder, it produces the reference project structure with search, llms.txt and OG images configured. Templates:
+next+fuma-docs-mdx(Next.js): the default, use it unless the user prefers another framework.waku,react-router,tanstack-start: when the user already uses that framework.+next+fuma-docs-mdx+static,react-router-spa,tanstack-start-spa: when the site must be fully static (hosted on a CDN).
Run it non-interactively (requires Node.js 22+), --yes uses defaults for unspecified options and --pm selects the package manager:
npx create-fumadocs-app@latest my-docs --template +next+fuma-docs-mdx --install --yesAll options: https://fumadocs.dev/docs/cli/create-fumadocs-app.mdx
Don't hand-write the setup from memory or pin older versions, the generated project is the source of truth. Don't edit the .source/ folder, it is generated by Fumadocs MDX.
3. Verify
Run npm run dev, open http://localhost:3000/docs and make sure pages render. Run npm run build before you finish, fix build errors by reading the docs of the relevant feature rather than removing it.
4. Add content
- Pages are MDX files in
content/docs, the folder structure defines the URLs. Every page needs atitlein its frontmatter, and ideally adescription. meta.jsonfiles order pages and folders: https://fumadocs.dev/docs/page-conventions.mdx and https://fumadocs.dev/docs/navigation.mdx- When migrating existing Markdown docs, move them as-is and keep their content, only adjust frontmatter and links. Supported syntax and components: https://fumadocs.dev/docs/markdown.mdx
- Site name and links live in
lib/layout.shared.tsx, GitHub info inlib/shared.ts(Next.js template; other templates put app code undersrc/, check the generated README).
5. Deploy separately
Deploy the docs app as its own project on the hosting provider: https://fumadocs.dev/docs/deploying.mdx
To serve it under the main domain, use a subdomain like docs.example.com, or route /docs/* from the main site to the docs deployment (e.g. Next.js Multi-Zones). Keep the docs under the default /docs route so both options work.
Apps Docs Public Robots (`apps/docs/public/robots.txt`)
User-agent: *
Allow: /
Host: https://fumadocs.dev
Sitemap: https://fumadocs.dev/sitemap.xml
Apps Docs Content Blog 2024 5 15 (`apps/docs/content/blog/2024-5-15.mdx`)
title: How Fumadocs works authors: [fuma] date: 2024-05-15
1 year ago, I was having fun with Next.js App Router.
While experimenting with it on my toy No Deploy, I planned to build documentation.
However, Nextra does not support App Router.
To handle this, I implemented a small documentation site with solely Contentlayer and the new features from App Router.
It was working great, looked blazing-fast and minimal.
I cloned the logic from No Deploy and built this documentation framework.
With a few months of development, it soon became powerful and stable.
It was originally named next-docs, I renamed it to Fumadocs as it conflicts with Next.js Docs.
Thanks to the support from Next.js community, I've received a lot of suggestions along the way.
Fumadocs is now a framework used by my libraries and some other amazing projects.
My Opinion
In Web development, most "robust" frameworks/libraries are incredibly heavy and fabulous, but it has indeed made our developer experience fancy.
On top of Javascript, people built bundlers, transpilers, and even Typescript.
It feels very surprising that Javascript, a high-level scripting language, is more similar to assembly code in modern Web development.
We rarely use them without things like Webpack. This also applies to CSS, at least in my experience, I seldom use CSS without PostCSS.
While they might be necessary for compatibility and DX, the landing of React Server Component and Next.js App Router made the experience even more mindblowing.
It feels like magic. These cunning magical frameworks, and web development miracles.
This kind of design is insane, but it also makes us mindlessly forget the boundaries.
Beginners use Metadata API, while they have no idea how meta tag works.
They put server-side logic in a server component, while they have no idea how expensive the calculation is.
Even when we looked at the code, we can't predict the result without running it in production mode.
I saw too many of these misconceptions.
This happens on many frameworks, they are overly magical.
I wanted to make it less-magic, and straightforward at least for most Next.js developers.
Fumadocs MDX
As the recommended content source, It is actually a webpack hack.
Since Next.js could only optimize static imports, it first transforms your .map.ts to a file that roughly yields:
export default [import("./my/file.mdx"), ...];And then transforms MDX files with a custom loader. It makes all magic possible, but it doesn't have the ability of lazy-loading MDX files.
Comparing to Nextra, it might be a suboptimal approach.
Nextra does it even easier, it directly transforms MDX files into pages. Because the Pages Router adapts Javascript files as a single page, it is possible.
In App Router, this is not possible anymore. Therefore, I didn't take this approach.
Fumadocs Core
The core of Fumadocs is a bunch of utilities and MDX plugins.
- Source API construct page trees from content source, integrated with other content providers.
- Headless components accelerate Fumadocs UI and other custom UI implementations.
- MDX plugins bring a perfect developer experience to all integrations.
- Search utilities make it way easier to implement document search.
In addition, it has also established the definitions of Page Tree and Page Conventions.
Overall, it is not yet a framework without Fumadocs UI.
In my opinion, the most valuable part of the codebase is MDX plugins.
I learned a lot more about ASTs and the eco-system of remark/rehype while working on them.
Absolutely an amazing experience.
Fumadocs UI
The UI implementation of Fumadocs using Tailwind CSS and Radix UI.
Its design system was inspired by Shadcn UI, using CSS variables for color utilities.
Although the structure of Fumadocs UI is even simpler than core, I've used some subtle hacks to solve the problem of "use client" directive.
The bundler I am using, TSX, can't handle nested structures like client components imported from a server component.
Therefore, I made a little hack to build server components and client components as an individual entry, then inject import statements after the process.
Also it took me some time to come up with the preset approach for integrating Fumadocs UI with Tailwind CSS projects.
Docs Generators
We have a few built-in integrations, like fumadocs-openapi which takes an OpenAPI schema and output MDX files.
For the OpenAPI one, it simply parses the schema and convert it to MDX file through string templates.
The Typescript integration does a bit more, it obtains type information with Typescript Compiler API. Based on the information, it yields MDX files.
You can use it inside a server component, which is how <AutoTypeTable /> works.
CI/CD
As a project with very few contributors, I built the CI/CD process as convenient as possible for a better efficiency.
The entire release process is handled by Changesets, and I wrote the scripts to update the template repository automatically.
It worked great so far.
Thanks
The Github repository of Fumadocs has reached 300 stars in 2024 March, it is a new achievement for me.
Welcome to give it a star to support my work!
Apps Docs Content Blog 2024 5 16 (`apps/docs/content/blog/2024-5-16.mdx`)
title: 500 Stars authors: [fuma] date: 2024-05-16
It was surprising when I first saw the video from Web Dev Cody, it felt like I had finally built something worth mentioning, instead of another neglected side project that nobody cares about.
So far I've worked on this project for about 1 year, and it is a precious experience to me. The best lecture that you can't find in a university.
Open Source
I received much feedback, issues and questions. Some are brutal but straightforward, like "please fix it, help me please" "Support XXX please". Some are kind and helpful, willing to provide a PR.
I felt both the good and bad sides of open source.
Sometimes, people are being eager and impulsive because of stress and may spread their frustration on the library maintainers.
Even myself, can be affected by stress or a bad mood.
People may hope for instant answers, and maintainers will face questions like "Why are you running away from my questions?".
I understand a library performing bad can cause your precious time to be wasted, but even if we put all our efforts in maintaining the library, it can't be flawless.
Since then, I feel I'm way more respectful to open-source project maintainers.
At least, I'll check my before before firing an issue on somebody else's Github repository, and try to be as respectful as I can with my poor language skills.
Issues
My favourite dev was Anthony Shew, I mentioned him because the issue he opened is the best I've ever seen in my open-source career.
He actually cared about my vision and opinion about the API design, and gave a really concise and constructive feature request.
Obviously, I was not a perfect, neither an experienced library dev.
Fumadocs wasn't capable of many things, such a well-explained feature request and idea is powerful. His passion is inspiring.
Before setting up the YAML issue template, there were very few issues that actually followed the issue template, most of them don't even provide a reproducible repository, or an explanation.
Open a proper issue, follow the instructions, and give maintainers some positive feedback. This is the biggest motivation you can give to maintainers without spending money.
Docs
It's fun to work on the docs of fumadocs. When first building it, I had very little experience on authoring documentation and tutorial content.
Reading through the feedback from developers, the most common problem is that they can't find the docs of something.
I realized the entire docs is unfriendly for beginners to start with, my friends are senior Next.js devs, and of course they are fine with that.
However, not every dev can understand the docs.
So recently, I started to re-write some of the sections, making it easier and appealing for beginners. Welcome to share your feedback on Github Discussions!
What is Next?
Web Development is an ever-evolving industry, but I believe the spirit of open source won't disappear.
I don't know what I will be building in the future, and that doesn't matter.
Let's build a better web!
Apps Docs Content Blog A Mistake (`apps/docs/content/blog/a-mistake.mdx`)
title: The early mistake of Fumadocs authors: [fuma] date: 2025-08-22
A Mistake
The early mistake of Fumadocs was Fumadocs CLI being too late and not getting enough mentions on docs.
To push the CLI approach further like the recent Shadcn CLI support, there are changes we have to make.
Early adopters will face problems if their customizations went beyond what our abstraction can mitigate.
For example: A small CSS change can still break hacky CSS customizations. We can only ensure backward compatibility for the ones scoped by CSS selectors, or exposed options:
#nd-sidebar {
/* this will work forever */
background-color: red;
}For CLI to work better, we need layout components to be entirely independent for modularization. A ideal state is that:
- Normal users can use exposed options, enjoying UI iterations from upstream.
- Advanced customizations can use Fumadocs CLI, avoiding upstream changes.
Same as the solution described in this post about UI Customization.
A Good Example
For lighter customizations like nuqs did, a PR to migrate from old versions #1056 can reduce code on user land and migration is simple.
The Problems
When your hacks are heavier, migration is much harder combined with the tech debt of your codebase.
Some problems I observed:
Page Tree API being Neglected
Page tree is actually a crucial idea of Fumadocs, unifying all navigation & linking of docs.
Even in the early days, it's already a robust API supporting features like Root Folder (since v12):
Some projects didn't rely on our file-based routing nor page tree API, which resulted in a lot of hacks around sidebar & loader().
Its idea is simple:
files -> Fumadocs -> page treepage tree -> navigation components (e.g. page footer & sidebar)Using it properly can already reduce a massive amount of work, we have a specs for Page Tree for hardcoded page trees.
CSS Variables being Neglected
Fumadocs is using CSS variables for all colors since the beginning, the naming is identical to Shadcn UI which makes customization easy.
But I noticed some are overriding the Tailwind CSS classes rather than colors, like:
#nd-sidebar .color-fd-background\/80 {
color: red;
}In fact we always support a simpler way:
#nd-sidebar {
--color-fd-foreground: red;
}Now we also have a Shadcn UI preset to reuse your colors from main app.
Early Reflections
I think the CLI approach is correct, it'll be the primary way for advanced customization.
My future plans:
- Continue improving Fumadocs CLI: adding install-time optimization to remove unused files according to your options (e.g. use existing sidebar component instead of installing one).
- A better versioning policy: Reduce abstraction for mitigating upstream changes, instead version the components directly (e.g. creating a new
fumadocs-ui/layouts/v2/pageinstead of a major UI change). - Add Fumadocs-native cloud features: More official integrations like monitoring/analytics feature designed for Fumadocs.
My biggest goal is to build my own dev team or startup, I realized my time is too limited to build the excellent software in my imaginary.
There's rough edges that I never found time to polish, and too many things to do - even if AI has handled the basic work.
That's all I want to say :)
Welcome for questions (via DM/GitHub), I'm happy to improve rough edges whenever I have time.
Apps Docs Content Blog Customise Ui (`apps/docs/content/blog/customise-ui.mdx`)
title: Rethink how we customize UI authors: [fuma] date: 2025-04-15
The Problem
Making a docs framework isn't challenging, the hardest problem is how I can make it perfect for everyone, from the perspective of users and developers.
Undoubtedly, this is a dilemma for all frameworks, or even library authors — You cannot write generic code that works out-of-the-box, there's no piece of software that is straight usable while being generic.
For example, headless UI libraries like Radix UI gave us the concept of generic UI components: bare UI primitives that we can customize according to our aesthetics.
However, you'll need time to tune them, these primitives are unusable without further customizations.
Yep it's 2025, Shadcn UI had led a "revolution" on Web Development. It's a great combination of headless UI and styled defaults that takes an extra step away!
But our problem remains unsolved, it might be preposterous that it becomes "unstyled" again when a vast majority of new sites adopted Shadcn UI.
Personally, I felt overwhelmed by nearly identical, ubiquitous flat designs that are obviously one of the default Shadcn UI styles. It has nothing to do with Shadcn UI, but the rest of developers who don't actually care about aesthetics and uniqueness.
Eventually, Shadcn-like design became the default styles, so once again, we will need to do a bit more to make it look stylish.
What should we do?
As a docs framework started from "being flexible", Fumadocs must be customisable, tuned to meet developers' preferences.
We will need to take customization seriously if DX is what we aim. And most importantly, it should encourage developers with a sense of beauty to customize.
The Solution
Typically, there are two types of developers:
those who want docs that just work
Fumadocs need to be opinionated by default such that it is immediately usable, and we only have to offer simplest options to change details. (e.g. colors, overriding components).
Most developers have no desire to install dozens of components just to change a tiny detail, which makes the Shadcn approach suboptimal.
those who need the perfect docs tailored to their preferences
The kernel of Fumadocs, or the spirit we're pursuing is the latter one. I crafted Fumadocs CLI, a version of Shadcn UI dedicated for Fumadocs, the whole solution is now simplified into a single command:
npx @fumadocs/cli customizeIt accounts for two types:
- Customize the default styles when the exposed options are inadequate.
- Roll their own layout from scratch.
The first one is self-evident, people will always have absurd needs that we have not even considered.
We can install the original components, so it's simply identical to the default UI which they've been using.
The latter one is simpler, we can offer the minimal template they can start customising from.
This methodology, inspired by ejection, brings some nice advantages:
Don't break anything when you upgrade Fumadocs
Fumadocs need to be opinionated. Hence, we need to iterate the UI frequently which apparently will break people's hacky customizations.
The charm of Fumadocs CLI is that once you've installed the layout, it stays the same forever regardless of future iterations on Fumadocs UI.
It gives the courage for Fumadocs UI to iterate, and developers to customize unburnt by the fear of upgrading dependencies.
Looks Clean
Just like Shadcn UI and Radix UI, Fumadocs UI has a core package (fumadocs-core), most generic logic will be abstracted by core.
What is Next?
The innovation of Shadcn UI is amazing, yet, I believe there's more problems Fumadocs can solve.
See Flags SDK and BetterAuth, you can't tell at the first glance if they're powered by Fumadocs.
I hope it can continue to be a "breakable" framework known for how customisable it is.
Apps Docs Content Blog Fumadocs Mdx Road Map (`apps/docs/content/blog/fumadocs-mdx-road-map.mdx`)
title: The Future of Fumadocs MDX date: 2025-12-05 authors: [fuma] tags: [Fumadocs MDX]
Overview of Fumadocs MDX
Fumadocs MDX serves as a content processing layer, initially developed for processing MDX files and as an alternative to Contentlayer.
Its primary functions include:
- Compiling and processing content.
- Offering interfaces to access compiled content.
- Ensuring type safety and compatibility across environments, such as browsers and Cloudflare Workers.
Comparison with CMS
Fumadocs MDX is not a content management system (CMS). A CMS encompasses more than content processing, it integrates features such as:
- Database Integration: For storing content.
- Version Control: To enable rollbacks, commits, and conflict resolution.
- Permission Management: To handle user access and roles.
- Authoring Workflow: Supporting stages like drafting, reviewing, and publishing.
Fumadocs MDX lacks these CMS capabilities independently, but they can be achieved through integration with Git and GitHub. This approach offers distinct benefits over traditional CMS solutions, including:
- Maintaining a unified history within Git, rather than distributing it across Git and a separate CMS.
- Composing content and code changes in a single pull request.
GitHub's collaboration tools, such as access controls, pull request reviews, and automation, support the implementation of authoring workflows.
Fumadocs MDX is not intended to evolve into a CMS; it remains a foundational content processing layer.
Roadmap
Fumadocs MDX is closely associated with Fumadocs, yet the content processing layer should ideally operate independently, without reliance on any specific documentation framework.
In the near future, certain components will transition to fuma-content, improving extensibility and introducing more APIs. Such as:
- Framework Agnostic: Fuma Content should work anywhere, including Vite, Turbopack/Webpack, and even JS runtimes.
- No Framework Defaults: the MDX preset of Fumadocs will be offered by
fumadocs-core, likely as a Fuma Content plugin. - Content Adapters: Expanding support beyond MDX and JSON files to include custom collection types, as well as an Obsidian plugin for handling editor-specific syntax.
- Zero breaking changes to Fumadocs MDX: we adopt Fuma Content under the hood, but surface APIs will not be changed. Ideally, it will be similar to how Vite leverages Rolldown.
API-wise, Fuma Content plugins will distinguish from bundler plugins, but plugins can leverage the built-in abstraction layer to implement loaders.
Otherwise, we will encourage Unplugin for advanced needs.
Although fuma-content focuses on content processing, it provides a foundation for developing a CMS layer, such as plugins for MDX editing or remote databases. It will be a bigger goal.
Fumadocs MDX extends beyond a simple MDX loader, its architecture supports multiple formats.
Although no renaming is planned, the name Fumadocs MDX no longer fully captures its scope.
The Current Progress
Fumadocs MDX already includes primitives for advanced setups, such as the new Workspace API, which enables content processing across multiple repositories.
We are welcome for feedbacks on GitHub Discussions!
Apps Docs Content Blog Make A Blog (`apps/docs/content/blog/make-a-blog.mdx`)
title: Making a Blog with Fumadocs authors: [fuma] date: 2024-12-15
Fumadocs is a docs framework, but it's also a powerful tool to manage content in Next.js. You can use Fumadocs to build a blog site along with docs, on a single Next.js app.
Overview
This guide helps you build a blog site with Fumadocs and Fumadocs MDX.
We will use Fumadocs MDX to manage the content, and implement our own UI with Tailwind CSS & Fumadocs UI.
Configure Content
Define a blogPosts collection.
import { defineCollections } from 'fumadocs-mdx/config';
import { pageSchema } from 'fumadocs-core/source/schema';
import { z } from 'zod';
export const blogPosts = defineCollections({
type: 'doc',
dir: 'content/blog',
// add required frontmatter properties
schema: pageSchema.extend({
author: z.string(),
date: z.string().date().or(z.date()),
}),
});Parse the output collection in source.ts:
import { loader } from 'fumadocs-core/source';
import { toFumadocsSource } from 'fumadocs-mdx/runtime/server';
import { blogPosts } from 'collections/server';
export const blog = loader({
baseUrl: '/blog',
source: toFumadocsSource(blogPosts, []),
});You can now access the content from blog.
Implement UI
Create an index page for blog posts.
By default, there should be a (home) route group with <HomeLayout /> inside.
Let's put the blog pages under it, this way we can get the nice navbar on our blog site.
import Link from 'next/link';
import { blog } from '@/lib/source';
export default function Home() {
const posts = blog.getPages();
return (
<main className="flex-1 w-full max-w-[1400px] mx-auto px-4 py-8">
<h1 className="text-4xl font-bold mb-8">Latest Blog Posts</h1>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{posts.map((post) => (
<Link
key={post.url}
href={post.url}
className="block bg-fd-secondary rounded-lg shadow-md overflow-hidden p-6"
>
<h2 className="text-xl font-semibold mb-2">{post.data.title}</h2>
<p className="mb-4">{post.data.description}</p>
</Link>
))}
</div>
</main>
);
}Colors like text-fd-muted-foreground are from the design system of Fumadocs UI, you may also use your own colors, or use Shadcn UI.
And create a page for blog posts.
Note that blog posts won't have nested slugs like /slug1/slug2. We don't need a catch-all route for blog posts.
/* Detailed source-code truncated for AI context efficiency. */And configure metadata:
import { notFound } from 'next/navigation';
import { blog } from '@/lib/source';
export async function generateMetadata(props: { params: Promise<{ slug: string }> }) {
const params = await props.params;
const page = blog.getPage([params.slug]);
if (!page) notFound();
return {
title: page.data.title,
description: page.data.description,
};
}Write Posts
The UI is now complete, you can write some blog posts under the content/blog directory, like:
---
title: Hello World
author: Fuma Nama
date: 2024-12-15
---
## Hello World
This is an example!After spinning up the development server with next dev, you should see the blog posts under /blog route.
Apps Docs Content Blog Mdx 12 (`apps/docs/content/blog/mdx-12.mdx`)
title: Fumadocs MDX v12 date: 2025-09-20 authors: [fuma] tags: [Release, Fumadocs MDX]
Overview
Fumadocs MDX v12 mainly aims to unify Fumadocs MDX on Vite and Next.js.
It contains renames to certain APIs (import paths are unaffected), migrations may be necessary.
Breaking Changes
Next.js
Removed content on page data in favour of getText().
import { source } from '@/lib/source';
const page = source.getPage(['...']);
console.log(page.data.content);import { source } from '@/lib/source';
const page = source.getPage(['...']);
console.log(await page.data.getText('raw'));API Renames on page data:
_file->info._file.absolutePath->info.fullPath.
import { source } from '@/lib/source';
const page = source.getPage(['...']);
console.log(page._file, page._file.absolutePath);import { source } from '@/lib/source';
const page = source.getPage(['...']);
console.log(page.info, page.info.fullPath);Improvements
Vite
Added APIs for info to access raw file info.
import { source } from '@/lib/source';
const page = source.getPage(['...']);
console.log(page.info);`getText()`
You can use it to access:
- Raw Markdown content.
- Processed Markdown before being compiled into HAST (HTML).
This is useful for llms.txt generation when used with docs generations tools & remark plugins.
import { source } from '@/lib/source';
const page = source.getPage(['...']);
console.log(await page.data.getText('raw'));
console.log(await page.data.getText('processed'));To access processed Markdown content, you need to enable includeProcessedMarkdown in collection config.
import { defineDocs } from 'fumadocs-mdx/config';
export default defineDocs({
docs: {
postprocess: {
includeProcessedMarkdown: true,
},
},
});Apps Docs Content Blog Mdx V10 Summary (`apps/docs/content/blog/mdx-v10-summary.mdx`)
title: Fumadocs MDX v10 Summary date: 2024-11-24 authors: [fuma] tags: [Fumadocs MDX]
Features
- More customizations via
source.config.ts, a new config file for your content. - Introduced collections.
- Turbopack support.
- Build-time data validation support.
Migrate from v9
Refactor Imports
Refactor the import in next.config.
From:
import createMDX from 'fumadocs-mdx/config';
const withMDX = createMDX();
/** @type {import('next').NextConfig} */
const config = {
reactStrictMode: true,
};
export default withMDX(config);To:
import { createMDX } from 'fumadocs-mdx/next';
const withMDX = createMDX();
/** @type {import('next').NextConfig} */
const config = {
reactStrictMode: true,
};
export default withMDX(config);Remove `mdx-components.tsx`
mdx-components.tsx is no longer used. It now allows only MDX components passed from MDX body's components prop.
import defaultMdxComponents from 'fumadocs-ui/mdx';
const MDX = page.data.body;
// set your MDX components with `components` prop
<MDX components={{ ...defaultMdxComponents }} />;This encourages explicit import of MDX components.
Previously, mdx-components.tsx worked by injecting an import to every compiled Markdown/MDX file.
It's somewhat unnecessary because you can always import the components explicitly, or replace default HTML tags like img from MDX body's components prop.
Define Collections
Collection is now introduced on source config file (source.config.ts).
It refers to a collection of files/content, such as Markdown files or JSON/YAML files.
Every collection has its own config, you can have customized Zod schema to validate its data, or collection-level MDX options for content collections.
You can create a source config file, and add the following:
import { defineDocs } from 'fumadocs-mdx/config';
export const { docs, meta } = defineDocs({
dir: 'content/docs',
});defineDocs declares two collections, one with doc type that scans content files (e.g. md/mdx), one with meta type that scans meta files (e.g. json).
Now you can generate types using fumadocs-mdx command, it's recommended to set it as a postinstall script.
{
"scripts": {
"postinstall": "fumadocs-mdx"
}
}When starting the development server, a .source folder will be generated, it contains all parsed collections/files.
You can add it to .gitignore, it will replace the old .map file.
To access the collections, import them from the folder with their original name in source.config.ts.
import { docs, meta } from '@/.source';Now to integrate it with Fumadocs framework, change your source.ts from:
import { map } from '@/.map';
import { createMDXSource } from 'fumadocs-mdx/runtime/next';
import { loader } from 'fumadocs-core/source';
export const { getPage, getPages, pageTree } = loader({
baseUrl: '/docs',
rootDir: 'docs',
source: createMDXSource(map),
});to:
import { docs, meta } from '@/.source';
import { createMDXSource } from 'fumadocs-mdx/runtime/next';
import { loader } from 'fumadocs-core/source';
export const source = loader({
baseUrl: '/docs',
source: createMDXSource(docs, meta),
});- we now recommend exporting the output of
loaderdirectly as a single variable. - schema option is no longer defined in
source.ts, it's handled bysource.config.ts. - it takes two collections:
docs(content) andmeta(meta.json).
You can leavemetaas an empty array if it is unused, so you can define only a collection fordocs.
`page.data`
When using with loader, you no longer need data.frontmatter to access frontmatter data.
It is merged into the page.data object.
page.data.frontmatter.title; // [!code --]
page.data.title; // [!code ++]MDX Options
Instead of passing them to next.config file, define a global config in source.config.ts:
import { defineConfig } from 'fumadocs-mdx/config';
export default defineConfig({
mdxOptions: {
// here!
},
});Collection Schema
The schema option of collection allow you to customize the validation schema, it accepts a Zod type.
For defineDocs, see schema.
Multiple Types
Same as before, you can call loader multiple times for different types (e.g. for docs and blog).
import { createMDXSource } from 'fumadocs-mdx/runtime/next';
import { loader } from 'fumadocs-core/source';
import { meta, docs, blog as blogPosts } from '@/.source';
export const source = loader({
baseUrl: '/docs',
source: createMDXSource(docs, meta),
});
export const blog = loader({
baseUrl: '/blog',
// as mentioned before, you can leave `meta` an empty array
source: createMDXSource(blogPosts, []),
});
export type DocsPage = (typeof source)['$inferPage'];
export type DocsMeta = (typeof source)['$inferMeta'];and the corresponding source.config.ts:
import { defineDocs, defineCollections, frontmatterSchema } from 'fumadocs-mdx/config';
import { z } from 'zod';
export const { docs, meta } = defineDocs({
dir: 'content/docs',
});
export const blog = defineCollections({
type: 'doc',
dir: 'content/blog',
schema: frontmatterSchema.extend({
author: z.string(),
date: z.string().date().or(z.date()).optional(),
}),
});Further Readings
You can read the latest docs of Fumadocs MDX for details.
Apps Docs Content Blog Mdx V10 (`apps/docs/content/blog/mdx-v10.mdx`)
title: Fumadocs MDX v10 date: 2024-09-06 authors: [fuma] tags: [Release, Fumadocs MDX]
The Problem
Fumadocs MDX worked great for docs. But we also want to prioritize flexibility and code organization.
Previously, it was a simple Webpack loader that turns MDX into JavaScript.
You pass the MDX processor options to the loader, it turns them into JavaScript files.
Then, a .map.ts will be exported:
export const map = {
'docs/index.mdx': import('./docs/index.mdx'),
'docs/guide.mdx': import('./docs/guide.mdx'),
};Your Next.js app will import the map file, and access the available MDX files.
This model works, but we started to see some problems:
No built-in way to define multiple collections:
For example, we have a directory for blog posts and a directory for docs pages.
On Fumadocs MDX, all these resources are transformed into a single object exported by
.map.ts:tsexport const map = { 'blog/post.mdx': import('./blog/post.mdx'), 'docs/index.mdx': import('./docs/index.mdx'), };We implemented a solution using Source API
rootDiroption, but this is not the ideal way.
This also gave us another problem:Different MDX Options for each collection:
Same as the above example, we have a
/blogdirectory for blog posts.
If we want to add a remark plugin that only work on blog posts, this is impossible with Fumadocs MDX.Once you apply a remark plugin, it's effective on all MDX files, including the MDX files in the docs directory.
Turbopack Compatibility:
Turbopack doesn't allow passing unserializable options to loaders.
However, the entire MDX, remark and rehype ecosystem use functions for plugins.
Functions are not serializable, we cannot migrate Fumadocs to Turbopack unless we find a seamless solution that supports Turbopack.Compile-time validation:
All schema validation cannot be done during build time because the MDX loader actually, doesn't know the collections you defined in
source.ts.Furthermore, the Zod schema is passed to the source adapter in
source.tsrather than the loader:tsimport { loader } from 'fumadocs-core/source'; import { createMDXSource } from 'fumadocs-mdx/runtime/next'; export const source = loader({ source: createMDXSource(map, { // schema }), });This loses some performance optimizations that can be possibly done on bundler level.
Untyped:
The exported
mapobject from.map.tsfile has a type ofunknown, it is only typed when using it with Source APIloader.This avoided the complexity to auto-generate types, but I wanted to make it typed and usable without Source API.
The Solution
Taking some references from Content Collections and Velite, I found it would be great to have a config file for Fumadocs MDX.
`source.config.ts`
We can make the syntax similar to Content Collections and other tools, to make the adoption process easier.
To define a collection:
import { z } from 'zod';
export const blog = defineCollections({
dir: './blog',
schema: z.object({
// the schema
}),
mdxOptions: {
// remark plugins?
},
});The MDX loader reads the config file, find the corresponding collection of the file, validate, and compile it using the options from collection.
This allows us to pass MDX options normally without breaking Turbopack's rules.
The Implementation
As the config file is written in TypeScript, we will need a bundler to read it.
I used esbuild, it is a performant bundler written in Go.
After bundling the config file, a dynamic import will work as expected.
await import('./source.compiled.mjs');`.map` file
We need a place to import the compiled collections.
Previously, we simply generate a .map.ts file with Webpack plugins.
It declares the types, with no actual data.
export declare const map: unknown;A loader will be used to transform .map.ts file into the output aforementioned:
export const map = {
'docs/index.mdx': import('./docs/index.mdx'),
'docs/guide.mdx': import('./docs/guide.mdx'),
};The generated .map.ts never changes because it doesn't depend on the config file.
No matter how you configure it, there'll be only a map object exported, with a type of unknown.
Now, we need to generate types for every collection, and the types may change as we change the collections.
The previous approach is no longer applicable.
I renamed the .map.ts file to .source/index, both index.d.ts and index.js are generated by Fumadocs MDX, instead of using a loader.
A map file generator is implemented, it reads the config file and generate output based on exported collections.
Auto-reload
We want to watch for changes:
- When a input file added/removed, add or remove relevant entries from the
.source/index.jsfile. - When the config file changed, re-compile affected files, and update generate types at
.source/index.d.ts.
I chose chokidar to watch file changes, it worked well.
The file watcher lives on next.config.mjs, it's independent to the MDX loader.
To notify bundlers when config files changed, we added a hash.
export const collection1 = [import('./docs/index.mdx?hash=hashOfConfigFile')];The file will be re-compiled as the config hash changes.
To optimize performance, we also added the collection name.
export const collection1 = [
import('./docs/index.mdx?hash=hashOfConfigFile&collection=collection1'),
];The loader obtains the collection of input file from resource query, without taking extra steps to detect its associated collection.
Result
A .source/index file will be generated, it is fully typed.
The files will be re-compiled as you modify the config file.
- Turbopack supported, we have a small example in the repo using Turbopack.
- Multiple Collections, with its own MDX options.
- Runtime + Build-time validation & transformation.
Questions
I think there is still room for improvement:
- Use a Turbopack-native thing to bundle the config file?
- Lazy bundle/import the main body of MDX file?
Please give me feedback about the redesign of Fumadocs MDX ;)
Apps Docs Content Blog Mdx V13 (`apps/docs/content/blog/mdx-v13.mdx`)
title: Fumadocs MDX v13 authors: [fuma] tags: [Release, Fumadocs MDX] date: 2025-10-23
Overview
Fumadocs MDX refined its API surface for better cross-framework compatibility and flexibility.
Plugins API
We introduced Plugins API, enabling extensible modifications to MDX configurations without bundler-specific code—ideal for file generation (e.g., types, indexes) or config alterations.
There's a json-schema plugin natively provided by Fumadocs MDX.
import jsonSchema from 'fumadocs-mdx/plugins/json-schema';
import { defineConfig, defineDocs } from 'fumadocs-mdx/config';
export const docs = defineDocs({
dir: 'content/docs',
});
export default defineConfig({
plugins: [jsonSchema()],
});Migration
Next.js
createMDXSource and resolveFiles are now relocated to fumadocs-mdx/runtime/next.
This should not affect majority of projects, as docs.toFumadocsSource() is now the preferred way.
Vite
The source.generated.ts file is now moved to .source/index.ts, you are recommended to import the .source folder (with a path alias).
{
"compilerOptions": {
"paths": {
// [!code ++]
"@/.source": [".source"]
}
}
}Migrate:
run dev server/typegen to generate a
.sourcefolder.import it over the original
source.generated.ts.note that both docs and
create-fumadocs-appare updated to.sourcefolder.
We also supported index file generation targeting other runtimes like Bun/Node.js.
import { defineConfig } from 'vite';
import mdx from 'fumadocs-mdx/vite';
import * as MdxConfig from './source.config';
export default defineConfig({
plugins: [
mdx(MdxConfig, {
generateIndexFile: {
runtime: 'node',
},
}),
// ...
],
});Bug Fixes
remark-include: fix Markdown stringify logics.postprocess.includeMDASTsupport.- (Vite) support
useContenton client loader for lint avoidance.
Apps Docs Content Blog Mdx V14 (`apps/docs/content/blog/mdx-v14.mdx`)
title: Fumadocs MDX 14 authors: [fuma] tags: [Release, Fumadocs MDX] date: 2025-11-16
We are pleased to announce Fumadocs MDX 14. This update introduces several major enhancements designed to:
- Improve flexibility, taking the plugin approach further.
- Improve performance & memory consumption.
- Improve browser/dynamic compilation support.
New Model
The output is now separated into:
.source/server: the content data available for server (e.g. RSC, SSR), should not be referenced in client code..source/browser: the data available for browser, with built-in serialization, chunking and loader layer..source/dynamic: interface for dynamic content compilation, only generated for collections withdynamic: true.
Breaking Changes
Entry Files
Fumadocs MDX now generates multiple entry files in .source folder.
To migrate, add/update the path aliases in tsconfig.json for new entry files.
{
"compilerOptions": {
"paths": {
// [!code ++]
"collections/*": ["./.source/*"]
}
}
}Import your collections from the entry files instead of @/.source:
// [!code --]
import { docs } from '@/.source';
// [!code ++]
import { docs } from 'collections/server';
// access collections on server-side, e.g.
import { loader } from 'fumadocs-core/source';
export const source = loader({
source: docs.toFumadocsSource(),
});// non-RSC environment (e.g. browser)
// [!code --:20]
import { docs } from '@/.source';
const clientLoader = createClientLoader(docs.doc, {
id: 'docs',
component({ toc, frontmatter, default: MDX }) {
return (
<DocsPage toc={toc}>
<DocsTitle>{frontmatter.title}</DocsTitle>
<DocsDescription>{frontmatter.description}</DocsDescription>
<DocsBody>
<MDX
components={{
...defaultMdxComponents,
}}
/>
</DocsBody>
</DocsPage>
);
},
});
// [!code ++:20]
import browserCollections from 'collections/browser';
const clientLoader = browserCollections.docs.createClientLoader({
component({ frontmatter, default: MDX }) {
return (
<DocsPage toc={toc}>
<DocsTitle>{frontmatter.title}</DocsTitle>
<DocsDescription>{frontmatter.description}</DocsDescription>
<DocsBody>
<MDX
components={{
...defaultMdxComponents,
}}
/>
</DocsBody>
</DocsPage>
);
},
});The new browser createClientLoader() should work seamlessly with old usage.
See Browser Entry for more examples of browserCollections.
Fumadocs Adapter
The adapter for loader() API is also changed.
For docs collection, create.sourceAsync() (Vite) is now consistent with Next.js:
import { docs } from 'collections/server';
import { loader } from 'fumadocs-core/source';
export const source = loader({
baseUrl: '/docs',
// [!code --]
source: await create.sourceAsync(docs.doc, docs.meta),
// [!code ++]
source: docs.toFumadocsSource(),
});For doc collections, createMDXSource() (Next.js) is renamed to toFumadocsSource().
If you use create.sourceAsync() (Vite) for doc collections, you should also switch to toFumadocsSource().
import { blogPosts } from 'collections/server';
import { toFumadocsSource } from 'fumadocs-mdx/runtime/server';
import { loader } from 'fumadocs-core/source';
export const blog = loader({
baseUrl: '/blog',
// [!code --] for Next.js
source: createMDXSource(blogPosts, []),
// [!code --] for Vite
source: await create.sourceAsync(blogPosts, []),
// [!code ++]
source: toFumadocsSource(blogPosts, []),
});Async & Dynamic Mode
On Next.js, Async Mode now aligns with the behaviour on Vite to generate async imports. This enables server-side chunking to improve memory usage.
import { defineDocs } from 'fumadocs-mdx/config';
export const docs = defineDocs({
dir: 'content/docs',
docs: {
// This no longer forces dynamic compilation! [!code ++]
async: true,
},
});Instead, we introduced a dynamic option to enable dynamic compilation. This is available for both Vite and Next.js.
import { defineDocs } from 'fumadocs-mdx/config';
export const docs = defineDocs({
dir: 'content/docs',
docs: {
// [!code ++]
dynamic: true,
},
});By enabling Async or Dynamic modes, the relevant collections are considered lazy-loaded, offering a load() function to load compiled properties.
Updated Signature for `postInstall()`
It now accepts the framework plugin options, you can customize more properties:
import { postInstall } from 'fumadocs-mdx/next';
// or
import { postInstall } from 'fumadocs-mdx/vite';
postInstall('source.config.ts');import { postInstall } from 'fumadocs-mdx/next';
// or
import { postInstall } from 'fumadocs-mdx/vite';
postInstall({ configPath: 'source.config.ts' });`getDefaultMDXOptions()` -> `applyMdxPreset()`
To support a broader range of presets and accommodate dynamic modes, getDefaultMDXOptions() has been renamed to applyMdxPreset().
import { defineCollections, getDefaultMDXOptions } from 'fumadocs-mdx/config';
import { myPlugin } from './remark-plugin';
export const blog = defineCollections({
type: 'doc',
mdxOptions: getDefaultMDXOptions({
remarkPlugins: [myPlugin],
// Alternatively, use a function to customize plugin order:
remarkPlugins: (v) => [myPlugin, ...v],
}),
});import { defineCollections, applyMdxPreset } from 'fumadocs-mdx/config';
import { myPlugin } from './remark-plugin';
export const blog = defineCollections({
type: 'doc',
mdxOptions: applyMdxPreset({
remarkPlugins: [myPlugin],
// Alternatively, use a function to customize plugin order:
remarkPlugins: (v) => [myPlugin, ...v],
}),
});Introduction of `lastModified` Plugin
The lastModifiedTime option has been replaced by a dedicated lastModified plugin.
This shift promotes a plugin-based architecture for better extensibility.
import { defineConfig } from 'fumadocs-mdx/config';
import lastModified from 'fumadocs-mdx/plugins/last-modified';
export default defineConfig({
// [!code ++]
plugins: [lastModified()],
});Removal of Multiple `dir` Support
Support for specifying multiple directories in a single collection has been discontinued. Instead, utilize the files property to filter and include specific files.
import { defineDocs } from 'fumadocs-mdx/config';
export const docs = defineDocs({
dir: 'content/guides',
docs: {
files: ['./i-love-fumadocs/**/*.{md,mdx}'],
},
});Disabling Default Generation of `extractedReferences`
The automatic generation of extractedReferences is no longer enabled by default. To retain this functionality, explicitly activate it via the postprocess option.
import { defineDocs } from 'fumadocs-mdx/config';
export const docs = defineDocs({
dir: 'content/docs',
docs: {
postprocess: {
// [!code ++]
extractLinkReferences: true,
},
},
});Vite: `generateIndexFile` -> `index`
The generateIndexFile option of our Vite plugin has been renamed to index.
import { defineConfig } from 'vite';
import mdx from 'fumadocs-mdx/vite';
import * as MdxConfig from './source.config';
export default defineConfig({
plugins: [
mdx(MdxConfig, {
index: {
// options
},
}),
],
});Patch Changes
- Resolved an issue with meta file validation when using Bun.
- Introduced optimizations to reduce unnecessary entry files re-generations.
Thank you for your continued support of Fumadocs. If you encounter any issues during the update, welcome to reach out through our GitHub Discussions.
Apps Docs Content Blog Mdx V15 (`apps/docs/content/blog/mdx-v15.mdx`)
title: Fumadocs MDX 15 authors: [fuma] tags: [Release, Fumadocs MDX] date: 2026-05-07
Overview
This is a boring major release mainly to reduce package size. Breaking changes:
fumadocs-coremust be16.7.0or above.next.configmust be ESM.
For Next.js users, if you encounter any error when upgrading, it could be due to ESM compatibility issues, you might be accidentally using CommonJS in next.config.
Consider to rename your config to next.config.mjs or next.config.mts.
If you prefer to use ESM in your entire project, set this in your package.json:
{
"name": "my-app",
// [!code ++]
"type": "module"
}For other unexpected breakages, please open a bug report on our GitHub repository.
Apps Docs Content Blog New Conventions (`apps/docs/content/blog/new-conventions.mdx`)
title: Adding new Conventions date: 2025-04-08 authors: [fuma]
`mdx-component.tsx`
Sometimes there are questions raised by beginners confused about where to add MDX components.
Although it's simply located in page.tsx, which shouldn't be difficult to find, I think some kind of conventions may help, so I decided to add back mdx-components.tsx.
I love to have fewer files, but considering create-fumadocs-app should also act as a starting point for beginners who may not be familiar with MDX, it's much more helpful to provide it by default.
Existing Projects
The convention is optional (only to make the learning curve smoother).
For existing users, you probably knew how to pass MDX components, and no further change is needed.
But you can still make one:
import defaultMdxComponents from 'fumadocs-ui/mdx';
import type { MDXComponents } from 'mdx/types';
export function getMDXComponents(components?: MDXComponents): MDXComponents {
return {
...defaultMdxComponents,
...components,
};
}And use it in your MDX component.
import { getMDXComponents } from '@/mdx-components';
<MDXContent
components={getMDXComponents({
// extend it
})}
/>;Deprecating ``
We now prefer a single <RootProvider /> for everything, replacing the need for `` with a i18n prop.
<RootProvider
i18n={
// i18n provider props
}
>
{children}
</RootProvider>This allows Root Provider to handle the hierarchy correctly without revealing too much complexity, for example, the correct order should be:
<FrameworkProvider>
<ThemesProvider>
<I18nProvider>
<SidebarProvider>
<SearchProvider>{children}</SearchProvider>
</SidebarProvider>
</I18nProvider>
</ThemesProvider>
</FrameworkProvider>Changing the hierarchy of different providers may result in problems since they actually rely on each other, now it is all integrated into <RootProvider />, including I18n configuration.
The new Internationalization guide requires Fumadocs 15.2.3 or above.
Apps Docs Content Blog Openapi V10 (`apps/docs/content/blog/openapi-v10.mdx`)
title: Fumadocs OpenAPI v10 authors: [fuma] tags: [Release, Fumadocs OpenAPI] date: 2025-11-06
We are pleased to announce the release of Fumadocs OpenAPI v10.
This major release focuses on enhancing customization capabilities and simplifying API surfaces, it is also an opportunity to drop older/deprecated APIs.
We have introduced multiple breaking changes that require attention during upgrades.
Breaking Changes
Redesign of `createOpenAPI` Usage
This redesign separates API page and server concerns, removes disablePlayground option in favor of playground.enabled, introduces client configuration support, and prefers client configs for adapters.
Isolation of API Page and Server:
Previously, all options are located in
createOpenAPI().ts title="lib/openapi.ts"import { createOpenAPI } from 'fumadocs-openapi/server'; import path from 'node:path'; export const openapi = createOpenAPI({ input: [path.resolve('./scalar.yaml')], proxyUrl: '/api/proxy', mediaAdapters: {/* adapters */}, shikiOptions: {/* options */}, });Now, you need to separate API page components and the server declaration.
ts tab="lib/openapi.ts"import { createOpenAPI } from 'fumadocs-openapi/server'; import path from 'node:path'; export const openapi = createOpenAPI({ input: [path.resolve('./scalar.yaml')], proxyUrl: '/api/proxy', });ts tab="components/api-page.tsx"import { openapi } from '@/lib/openapi'; import { createAPIPage } from 'fumadocs-openapi/ui'; export const APIPage = createAPIPage(openapi, { mediaAdapters: {/* adapters */}, shikiOptions: {/* options */}, });More Options for Playground:
Useplayground.enabledinstead ofdisablePlayground.typescript title="components/api-page.tsx"export const APIPage = createAPIPage(openapi, { playground: { enabled: false, }, });Client Configuration Support:
The options are now split into server/client configs, some options are moved to the client-side config,
typescript tab="components/api-page.tsx"import client from './api-page.client'; export const APIPage = createAPIPage(openapi, { client, });typescript tab="components/api-page.client.tsx"'use client'; import { defineClientConfig } from 'fumadocs-openapi/ui/client'; export default defineClientConfig({ playground: { transformAuthInputs: (inputs) => [/* modified inputs */], }, });Client-Side Media Adapters:
Also forward your media adapters via client config.typescript title="components/api-page.client.tsx"'use client'; import { defineClientConfig } from 'fumadocs-openapi/ui/client'; import { adapters } from './my-media-adapters'; export default defineClientConfig({ mediaAdapters: adapters, });
Renaming Options
The option content.showExampleInFields has been renamed to schemaUI.showExample.
Removal of Centralized `renderer` and `fields` APIs
Fumadocs OpenAPI now prefers per-feature customizations, eliminating the centralized renderer API. Custom render functions are now specified directly in the content option when creating an API page.
import { openapi } from '@/lib/openapi';
import { createAPIPage } from 'fumadocs-openapi/ui';
export const APIPage = createAPIPage(openapi, {
content: {
renderResponseTabs,
renderAPIExampleLayout,
renderAPIExampleUsageTabs,
},
});For migrating Playground fields options, utilize client-side render APIs:
'use client';
import { defineClientConfig } from 'fumadocs-openapi/ui/client';
export default defineClientConfig({
playground: {
renderParameterField: (fieldName, field) => {
/* custom implementation */
},
},
});Additional layout customizations are available:
import { openapi } from '@/lib/openapi';
import { createAPIPage } from 'fumadocs-openapi/ui';
export const APIPage = createAPIPage(openapi, {
content: {
renderResponseTabs: (tabs) => <div>{/* custom tabs */}</div>,
renderAPIExampleLayout: ({ selector, usageTabs, responseTabs }) => (
<div>{/* custom layout */}</div>
),
renderAPIExampleUsageTabs: (generators) => <div>{/* custom tabs */}</div>,
renderPageLayout: ({ operations, webhooks }) => <div>{/* custom page */}</div>,
renderOperationLayout: (slots) => <div>{/* custom operation */}</div>,
renderWebhookLayout: ({ header, authSchemes, parameters, body, responses, callbacks }) => (
<div>{/* custom webhook */}</div>
),
},
});`generateFiles()` Requires OpenAPI Server
File generation is now handled within the OpenAPI server. The input field for generateFiles must reference the server instance rather than a string array of file paths.
import { openapi } from '@/lib/openapi';
void generateFiles({
input: ['./products.yaml'],
output: './content/docs',
});import { generateFiles } from 'fumadocs-openapi';
import { openapi } from '@/lib/openapi';
void generateFiles({
input: openapi,
output: './content/docs',
});New Features
Storage Key Prefix
v10 introduced the storageKeyPrefix option to prevent state bleed across multiple OpenAPI instances by isolating localStorage keys.
'use client';
import { defineClientConfig } from 'fumadocs-openapi/ui/client';
export default defineClientConfig({
storageKeyPrefix: 'fumadocs-openapi-custom-',
});Bug Fixes
- TypeScript Schema Output Correction: Resolved incorrect TypeScript schema generation. Code formatting is disabled in this process to enhance performance.
We recommend reviewing your implementation against these changes to leverage the improved flexibility and performance.
If you encounter issues, welcome to contribute feedback to our GitHub repository.
Apps Docs Content Blog Openapi V11 (`apps/docs/content/blog/openapi-v11.mdx`)
title: Fumadocs OpenAPI v11 authors: [fuma] tags: [Release, Fumadocs OpenAPI] date: 2026-06-11
We are pleased to announce the release of Fumadocs OpenAPI v11.
This major release unifies the RSC and client APIs, removes deprecated options accumulated since v10, and simplifies the server surface.
This release requires attention during upgrades. Run a type check after migrating:
npm run types:checkIf you haven't migrated to v10 yet, read the Fumadocs OpenAPI v10 announcement first.
Unify RSC & Client APIs
createAPIPage() and createClientAPIPage() are replaced by a single createOpenAPIPage() API. The component no longer accepts an OpenAPIServer instance or a separate client config, all options live in one client component.
The fumadocs-openapi/ui/client subpath export is removed. Merge any defineClientConfig() options into createOpenAPIPage() directly.
import { openapi } from '@/lib/openapi';
import { createAPIPage } from 'fumadocs-openapi/ui';
import client from './api-page.client';
export const APIPage = createAPIPage(openapi, {
client,
mediaAdapters: {/* ... */},
});'use client';
import { defineClientConfig } from 'fumadocs-openapi/ui/client';
export default defineClientConfig({
playground: {
transformAuthInputs: (inputs) => inputs,
},
storageKeyPrefix: 'my-openapi-',
});'use client';
import { createOpenAPIPage } from 'fumadocs-openapi/ui';
export const OpenAPIPage = createOpenAPIPage({
mediaAdapters: {/* ... */},
playground: {
transformAuthInputs: (inputs) => inputs,
},
storageKeyPrefix: 'my-openapi-',
});api-page.tsx must be a client component. Note that for existing non-RSC usages, old APIs are only deprecated, migrating to the new createOpenAPIPage() is optional, and API surface is identical.
Pass Page Props from Server
The server is responsible for passing OpenAPI data to <OpenAPIPage />.
<Tabs items={['Virtual Files', 'MDX Files']}>
Use page.data.getOpenAPIPageProps() when rendering OpenAPI pages from virtual files:
import { OpenAPIPage } from '@/components/api-page';
export default function Page({ slug }) {
const page = source.getPage(slug);
if (page.type === 'openapi') {
return (
<DocsPage full>
<DocsTitle>{page.data.title}</DocsTitle>
<DocsBody>
<OpenAPIPage {...page.data.getOpenAPIPageProps()} />
</DocsBody>
</DocsPage>
);
}
// ...
}Use openapi.preloadOpenAPIPage() when rendering generated MDX files:
import { openapi } from '@/lib/openapi';
import { OpenAPIPage } from '@/components/api-page';
export default async function Page({ slug }) {
const page = source.getPage(slug);
return (
<MdxContent
components={getMDXComponents({
OpenAPIPage: async (props) => (
<OpenAPIPage {...await openapi.preloadOpenAPIPage(page)} {...props} />
),
})}
/>
);
}Server & Loader
`getSchema()` Return Value
getSchema() on virtual pages and the OpenAPI server no longer includes a dereferenced document. It now returns the bundled schema only.
const schema = page.data.getSchema();
// dereferenced paths, components, etc.
schema.dereferenced.paths;const schema = page.data.getSchema();
// bundled (non-dereferenced) document
schema.bundled;Dereferencing happens at render time inside <OpenAPIPage /> for better performance.
`input` Option
The whole-map factory form of input is removed. Use a record instead: each value can be a file path, URL, inline document, or a per-entry async function.
import { createOpenAPI } from 'fumadocs-openapi/server';
export const openapi = createOpenAPI({
input: async () => {
const res = await fetch('https://example.com/openapi.json');
return {
my_api: await res.json(),
};
},
});import { createOpenAPI } from 'fumadocs-openapi/server';
export const openapi = createOpenAPI({
input: {
my_api: async () => {
const res = await fetch('https://example.com/openapi.json');
return res.json();
},
},
});Customization Callbacks
Several callbacks now receive richer context about the current operation.
`generateCodeSamples`
import { createCodeSample } from 'fumadocs-openapi/server';
export const OpenAPIPage = createOpenAPIPage({
generateCodeSamples: (method) => [
createCodeSample({
label: 'Custom',
lang: 'js',
generate(data) {
return `fetch('${data.url}')`;
},
}),
],
});export const OpenAPIPage = createOpenAPIPage({
generateCodeSamples: ({ operation, method, pathItem }) => [
{
label: 'Custom',
lang: 'js',
generate(data) {
return `fetch('${data.url}')`;
},
},
],
});`renderOperationLayout`
export const OpenAPIPage = createOpenAPIPage({
content: {
renderOperationLayout: (slots, ctx, method) => <div>{slots.header}</div>,
},
});export const OpenAPIPage = createOpenAPIPage({
content: {
renderOperationLayout: (slots, { operation, method, pathItem, ctx }) => (
<div>{slots.header}</div>
),
},
});`playground.render`
export const OpenAPIPage = createOpenAPIPage({
playground: {
render: (method) => <MyPlayground method={method} />,
},
});export const OpenAPIPage = createOpenAPIPage({
playground: {
render: ({ operation, method, pathItem, path, ctx }) => (
<MyPlayground operation={operation} method={method} path={path} />
),
},
});Dropped Deprecated APIs
The following APIs are removed in v11. Use the replacements listed below.
| Removed | Replacement |
|---|---|
transformerOpenAPI() |
openapiPlugin() |
generateTypeScriptSchema() |
generateTypeScriptDefinitions() |
playground.requestTimeout |
playground.fetchOptions.requestTimeout |
allowedUrls (proxy) |
allowedOrigins or filterRequest |
groupStyle (generateFiles / staticSource) |
folderStyle |
Loader Plugin
import { transformerOpenAPI } from 'fumadocs-openapi/server';
export const source = loader({
pageTree: {
transformers: [transformerOpenAPI()],
},
});import { openapiPlugin } from 'fumadocs-openapi/server';
export const source = loader({
plugins: [openapiPlugin()],
});Proxy Server
export const { GET, POST } = openapi.createProxy({
allowedUrls: ['https://example.com'],
});export const { GET, POST } = openapi.createProxy({
allowedOrigins: ['https://example.com'],
});Playground Request Timeout
export const OpenAPIPage = createOpenAPIPage({
playground: {
requestTimeout: 30,
},
});export const OpenAPIPage = createOpenAPIPage({
playground: {
fetchOptions: {
requestTimeout: 30,
},
},
});TypeScript Definitions
export const OpenAPIPage = createOpenAPIPage({
generateTypeScriptSchema: async (method, statusCode, contentType, ctx) => {
// ...
},
});export const OpenAPIPage = createOpenAPIPage({
generateTypeScriptDefinitions: async (schema, ctx) => {
// ...
},
});`generateFiles()` Context
The documents field is removed from the beforeWrite and index.items callback context. Access schemas from the OpenAPI server instead.
void generateFiles({
input: openapi,
output: './content/docs',
beforeWrite(files) {
const doc = this.documents['petstore.yaml'];
// ...
},
});void generateFiles({
input: openapi,
output: './content/docs',
async beforeWrite(files) {
const schemas = await openapi.getSchemas();
const doc = schemas['petstore.yaml'];
// ...
},
});Similarly, rename groupStyle to folderStyle when generating meta.json files:
void generateFiles({
meta: {
groupStyle: 'separator',
},
});void generateFiles({
meta: {
folderStyle: 'separator',
},
});Deprecated APIs
The following APIs are deprecated but still available in v11, migration is recommended.
Virtual Files
The data methods on page.data are unified into getOpenAPIPageProps().
| Before | After |
|---|---|
getAPIPageProps() |
getOpenAPIPageProps() |
getClientAPIPageProps() |
getOpenAPIPageProps() |
Apps Docs Content Blog Openapi V12 (`apps/docs/content/blog/openapi-v12.mdx`)
title: Fumadocs OpenAPI v12 authors: [fuma] tags: [Release, Fumadocs OpenAPI] date: 2026-09-20
We are pleased to announce the release of Fumadocs OpenAPI v12.
An API page used to be one component with a long list of render options. v12 splits it in two:
- a headless layer that owns the state and logic,
- the UI it renders through, which you can install and edit.
Options remain for the small tweaks, the rest is yours to replace.
Read the Fumadocs OpenAPI v11 announcement first.
Headless API Pages
Everything outside fumadocs-openapi/ui renders nothing on its own.
| Path | Module |
|---|---|
fumadocs-openapi |
the page: the document, its servers and your components |
fumadocs-openapi/operation |
an operation: its details, example requests and code usages |
fumadocs-openapi/playground |
the auth state and the fetcher of the playground |
fumadocs-openapi/requests |
encoding the data of an example into a real request |
fumadocs-openapi/requests/generators |
turning that request into code usages |
createOpenAPIRenderer() takes your components and returns an <OpenAPIPage /> that accepts the props of generated pages:
'use client';
import { createOpenAPIRenderer } from 'fumadocs-openapi';
import { Operation } from '@/components/my-operation';
import { SchemaUI } from '@/components/my-schema';
export const OpenAPIPage = createOpenAPIRenderer({
components: { Operation, SchemaUI },
});Markdown, CodeBlock and Heading are filled in when you leave them out, so a custom UI starts from two components.
Under the page, hooks read its state:
| Hook | Returns |
|---|---|
useOpenAPI() |
the dereferenced document (doc) and request options |
useComponents() |
the components passed to the page |
useServer() |
the selected server, its variables, and resolveUrl(pathname) |
useOperation() |
the operation with its details resolved |
useExampleRequests() |
the example requests, the selected one, select() and update() |
useCodeUsage(id) |
the code generated for the selected example |
useResponseExamples() |
responses with example values of their preferred media type |
See Headless for the full surface.
Install the UI
The entire UI of API pages, built on that layer, installed with Fumadocs CLI:
npx @fumadocs/cli add openapi/pageYour components/api-page.tsx is no longer needed:
import { OpenAPIPage } from '@/components/api-page'; // [!code --]
import { OpenAPIPage } from '@/components/openapi/page'; // [!code ++]Or take one part of it, and pass the rest through components:
'use client';
import { createOpenAPIPage } from 'fumadocs-openapi/ui';
import { Operation } from '@/components/openapi/operation';
import { Schema } from '@/components/api/schema';
export const OpenAPIPage = createOpenAPIPage({
components: { Operation, SchemaUI: Schema },
});| Part | Install with |
|---|---|
| Full page | openapi/page |
| Operation UI | openapi/operation |
| API Playground | openapi/playground |
| Schema UI | api-docs/schema |
What you install is UI, not a fork:
- the request pipeline of the playground stays in the package, an edited playground still drives the real one.
SelectandInputfollow the Shadcn UI API, a project that already has them keeps its own.
`@fumadocs/json-schema`
The JSON Schema utilities of API pages are their own package, with no Fumadocs dependencies:
import { dereference, matches, mergeAllOf, sample, stringify } from '@fumadocs/json-schema';
import { bundle } from '@fumadocs/json-schema/bundle';bundle()is a separate entry because it reads files and URLs, everything else runs in the browser.@fumadocs/json-schema/reactturns a schema into the data an API page draws.generateSchemaUI()flattens it, so your UI walks a map from$rootinstead of recursing.@fumadocs/api-docsis no longer published, its UI is now bundled into the integrations or installed with the CLI.
| Before | Now |
|---|---|
ParsedSchema |
JsonSchema |
NoReference / NoReferenceSwallow |
Dereferenced / DereferencedShallow |
dereferenceShallow(schema) |
dereference(schema) |
matchesSchema(schema, value) |
matches(schema, value) |
typeMatches(value, type) |
matchesType(value, type) |
schemaToString(schema, FormatFlags.UseAlias) |
stringify(schema, { alias: true }) |
| Before | Now |
|---|---|
@fumadocs/api-docs/schema/* |
@fumadocs/json-schema |
@fumadocs/api-docs/components/schema* |
npx @fumadocs/cli add api-docs/schema |
@fumadocs/api-docs/components/* (the UI) |
installed with the component that uses it |
@fumadocs/api-docs/i18n |
the integration's own Translations covers its keys |
@fumadocs/api-docs/css/preset.css |
already included by the integration's preset |
The CLI still installs the Schema UI, now under api-docs/schema.
Smaller Bundles
createOpenAPIBaseRenderer() is the renderer with nothing built in, it bundles only what you pass:
createOpenAPIBaseRenderer({
shiki,
codeUsages: createCodeUsageGeneratorRegistry().register(curl),
components: { Operation, SchemaUI },
});createOpenAPIRenderer() and fumadocs-openapi/ui register the full Shiki bundle, every code usage generator and TypeScript definitions for you.
The package entry is client-safe too. generateFiles() reads and writes files, so it ships stubbed under the browser condition, and a client component importing the renderer no longer pulls node:fs into the bundle.
Migration
Run a type check after upgrading:
npm run types:checkComponents installed from v11 with Fumadocs CLI, like the API playground, use the old hooks. Reinstall them.
Options
Render options that returned a component are components now:
createOpenAPIPage({
- schemaUI: { render: (props) => <Schema {...props} /> },
- renderHeading: (props, depth) => <Heading depth={depth} {...props} />,
- renderCodeBlock: (props) => <CodeBlock {...props} />,
- renderMarkdown: (md) => <Markdown md={md} />,
+ components: { SchemaUI: Schema, Heading, CodeBlock, Markdown },
});playground.provideris removed, the page provides the auth state of the playground.playground.renderandgenerateTypeScriptDefinitionsno longer receivectx. Read the document fromuseOpenAPI().doc, or thedocpassed togenerateTypeScriptDefinitions.operation.APIExampleSelectoris removed, installopenapi/operationand edit the selector inusage-tabs.tsx.`` reads the operation from
useOperation(), itsroute,method,operationandpathItemprops are gone:tsxplayground: { render: () => <PlaygroundClient writeOnly readOnly={false} />, }The
ctxofcontentrender options is the render options of the page, whatuseRenderContext()returns.ctx.schema,ctx.SchemaUIandctx._default_processMarkdownare removed, read them fromuseOpenAPI().docanduseComponents().
Hooks
The hooks of fumadocs-openapi/ui are replaced by the headless ones:
| v11 | v12 |
|---|---|
useRenderContext() |
useOpenAPI() (schema is renamed to doc), useComponents(), useRenderContext() |
useServerContext() |
useServer() |
useOperationContext() |
useOperation(), useExampleRequests(), useExampleRequest() (on /operation) |
- const { route, examples, example, setExample, setExampleData } = useOperationContext();
+ const { path } = useOperation();
+ const { items, selected, select, update } = useExampleRequests();
+ // data of the selected example, replaces `addListener()`
+ const data = useExampleRequest();useStorageKey() is removed, read the prefix from the page:
const { storageKeyPrefix } = useOpenAPI();
localStorage.getItem(`${storageKeyPrefix}my-key`);Removed & Moved
| Removed | Use |
|---|---|
fumadocs-openapi/ui/create-client |
createOpenAPIPage() from fumadocs-openapi/ui |
ApiPageProps |
OpenAPIPageProps |
OperationItem and WebhookItem of fumadocs-openapi/ui |
import them from fumadocs-openapi |
getAPIPageProps() and getClientAPIPageProps() |
getOpenAPIPageProps() |
defineI18nOpenAPI() |
i18n.translations().extend(openapiTranslations()) |
APIPage of MDX components |
OpenAPIPage, generated files only render it |
| Was | Now |
|---|---|
fumadocs-openapi/playground/client |
fumadocs-openapi/ui/playground/client |
fumadocs-openapi/scalar |
fumadocs-openapi/ui/scalar |
GenerateTypeScriptDefinitionsContext comes from the package entry, next to the runtime option it types.
Also in v12
- Code samples from
x-codeSamplesandgenerateCodeSamplesare rendered when their id isn't a built-in generator. SchemaData.infoTagsentries are data the UI renders:{ label, value, block? }or{ label, list }. Custom nodes ({ node }) still work, code readingtag.nodemust handle all shapes.
Thanks for supporting Fumadocs, share your upgrade experience on GitHub :)
Apps Docs Content Blog Openapi V9 (`apps/docs/content/blog/openapi-v9.mdx`)
title: Fumadocs OpenAPI v9 date: 2025-05-25 authors: [fuma] tags: [Release, Fumadocs OpenAPI]
Overview
This breaking change is mainly made to address problems of generateFiles() function.
Previously, it was a simple function to generate multiple MDX files/pages from your OpenAPI schema, same as every docs generator.
We tested it on public OpenAPI schemas such as Unkey, Vercel and examples from Scalar and Swagger's Museum schema.
Since these schemas were written for different docs solutions, or even their custom ones.
We made generateFiles() too complicated to be capable of producing best results for various schema styles, and it's even harder to customize the generated file paths of API docs.
This update also include:
- Better UI for schema & playground body input
- Improved type-safety for
generateFiles()
Breaking Changes
The algorithm for generating file paths is changed:
meta.jsonwill no longer be generated, add as your wanted.per: operation: The generated path will be identical to your operation id. If not defined, it takes your endpoint path instead.
Group By
The behaviour of groupBy API is also changed.
| value | output |
|---|---|
| tag | {tag}/{operationId ?? endpoint_path}.mdx |
| route | {endpoint_path}/{method}.mdx (ignores name option) |
| none | {operationId ?? endpoint_path}.mdx (default) |
Customize Output
The usage of name option is renewed, it can be used to customize the output path of files.
import { generateFiles } from 'fumadocs-openapi';
void generateFiles({
name: (output, document) => {
// page info
console.log(output);
// parsed OpenAPI schema
console.log(document);
return 'my-dir/filename';
},
});Migration
You can start using the latest algorithm, or keep the current behaviour with:
import { generateFiles } from 'fumadocs-openapi';
void generateFiles({
name: {
algorithm: 'v1',
},
});Note that it won't create meta.json anymore even with algorithm set to v1, you can keep your current generated meta.json files.
Apps Docs Content Blog V12 After (`apps/docs/content/blog/v12-after.mdx`)
title: Fumadocs 12.0.5 date: 2024-06-13 authors: [fuma] tags: [Release]
Some Improvements
After releasing Fumadocs 12, some improvements were made to the new UI.
TOC Popover
Previously, this was only available on larger devices. Now, it's collapsed to a popover on smaller devices.
Links Menu
To make the sidebar look even better, we moved navigation links to the links menu (the three-dots button at the top corner).
The recommended design is:
- Docs related links: Use
<RootToggle />or include it in page trees. - Other links: Use the Links API of docs layout.
Breadcrumbs
Removed the duplicated page name from breadcrumbs, now it only shows the folder names.
Also, you can enable includeSeparators to show separators on your breadcrumbs component.
The new breadcrumbs look cleaner and match the design even better.
Adaption
Some docs sites such as https://yeecord.com and https://turbo.build have adopted the new UI, I'm very excited about it.
Removing the navbar seemed to be a very risky move to me, almost no documentation framework has done it, and I was expecting some complaints about the new design.
Luckily, most people are satisfied with the new UI.
The new design was originally inspired by Arc, their sidebar looked very impressive to me.
Although I soon discovered the docs site of Linear also doesn't have a navbar, Arc is still the first inspiration of the redesign.
I'm grateful that many people gave me their feedback about the new design, so that I can keep making Fumadocs a better framework to use.
Let's make better docs.
Apps Docs Content Blog V12 (`apps/docs/content/blog/v12.mdx`)
title: Fumadocs 12 date: 2024-06-09 authors: [fuma] tags: [Release]
What's New?
Fumadocs v12 mainly aims to improve the UI, prioritizing content and reading experience.
New UI
After observing large docs sites like https://turbo.build, I found that the navbar took too much space on the screen, while it only contains links to Blog, Showcase, GitHub, etc.
They are not necessarily related to the docs, placing them at the top of our screen doesn't bring a better reading experience.
All these links are now moved to the sidebar, allowing a clean, minimal view of the docs.
I also noticed the docs start to look messy as your content grows. To ameliorate this, the sidebar now includes a border and background by default.
This helps readers to distinguish navigation elements and content easier.
The sidebar will always be placed at the left of the screen, having a bigger space on large viewports.
I believe the new UI still has room for improvement. Welcome to report UI issues, or leave feedback!
Better Multi Page Trees
We supported multiple page trees at a very early version of Fumadocs. However, it lacks proper explanations and guides to configure it.
In the past, you needed to implement a navigation element to switch between page trees and mark the folder as a root folder.
Now, you can use the <RootToggle /> component directly, and the docs has a better explanation of it.
Page Tree Post Processors
You can now attach properties to page trees easily with the Source API.
import { loader } from 'fumadocs-core/source';
export const utils = loader({
pageTree: {
attachFile(node) {
// modify the node
return node;
},
},
});Improved i18n Support
The language switch is now added to Docs Layout.
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
export default function Layout({ children }: { children: React.ReactNode }) {
return <DocsLayout i18n>{children}</DocsLayout>;
}Note that <LanguageSelect /> component is now replaced by <LanguageToggle />, make sure to remove references to the old component.
Breaking
UI
Remove deprecated option enableThemeProvider from Root Provider. Use theme.enabled instead.
Core
Remove deprecated option minWidth from Sidebar component. Use blockScrollingWidth instead
Fixes
UI
- Fix problems with twoslash codeblocks
- Apply typography styles to headings
- Support translation for theme label
OpenAPI
- Fix nullable types cannot be detected
Core
- Remark Headings: Support code syntax in headings
Create Fumadocs App
- Add
layout.config.tsxfile for sharing layout options
Apps Docs Content Blog V13 (`apps/docs/content/blog/v13.mdx`)
title: Fumadocs v13 authors: [fuma] tags: [Release] date: 2024-07-28
Introduction
Fumadocs v13 has been released. It aims to fix the CSS pollution problem and remove deprecated APIs.
New Features
Better Code Blocks
Now supports keeping the original background generated by Shiki on CodeBlock component.
import { Pre, CodeBlock } from 'fumadocs-ui/components/codeblock';
<MDX
components={{
pre: ({ ref: _ref, ...props }) => (
<CodeBlock keepBackground {...props}>
<Pre>{props.children}</Pre>
</CodeBlock>
),
}}
/>;Callout
Callout is now a default MDX component, you can use it in MDX files without an import, or manually adding it to MDX components.
<Callout type="warn">Hello World</Callout>New Headless TOC
The headless component of Table of Contents (TOC) now has a separate scroll container provider.
import * as Base from 'fumadocs-core/toc';
return (
<Base.AnchorProvider>
<Base.ScrollProvider>
<Base.TOCItem />
<Base.TOCItem />
</Base.ScrollProvider>
</Base.AnchorProvider>
);The anchor provider can be placed anywhere.
Opt-out of Container
Now supports disabling the default container styles of Tailwind CSS added by Fumadocs UI.
import { createPreset } from 'fumadocs-ui/tailwind-plugin';
/** @type {import('tailwindcss').Config} */
export default {
presets: [
createPreset({
modifyContainer: false,
}),
],
};Admonition Syntax
In Docusaurus, there's an Admonition syntax.
For people migrating from Docusaurus, you can enable the new remark plugin to support the Admonition syntax.
See Remark Admonition.
Breaking Changes
Prefix to colors, animations, and utilities
Previously, the Tailwind CSS plugin of Fumadocs UI added colors (including primary, secondary) which conflicted with Shadcn UI and other design systems.
A fd- prefix is added to them to allow the flexibility to have a separated design system on docs.
To use the Fumadocs UI colors on your primary app, enable the addGlobalColors option.
import { createPreset } from 'fumadocs-ui/tailwind-plugin';
/** @type {import('tailwindcss').Config} */
export default {
presets: [
createPreset({
addGlobalColors: true,
}),
],
};This adds the colors without the fd- prefix.
Or you can just reference them with the fd- prefix, like bg-fd-background.
Tailwind CSS Plugin ESM-only
Since Tailwind CSS supports TypeScript configuration, it makes sense to migrate the entire plugin to ESM.
Use ESM syntax in your configuration.
import { createPreset } from 'fumadocs-ui/tailwind-plugin';
/** @type {import('tailwindcss').Config} */
export default {
content: [
'./app/**/*.{ts,tsx}',
// others
],
presets: [createPreset()],
};Remove `RollButton` component
RollButton was introduced because there were no "Table Of Contents" on mobile viewports.
Now users can use the TOC Popover to switch between headings, it is no longer a suitable design for Fumadocs UI.
You may copy the last implementation of RollButton.
Better i18n
Now the I18nProvider component requires the locale prop instead of parsing it from pathname.
This fixed some bugs with the I18n middleware.
We also changed the usage of translations to reduce extra translations loaded on the client side.
You have to pass the active translations directly.
locales is passed as the available options on the Language Select component.
import { RootProvider } from 'fumadocs-ui/provider';
import type { ReactNode } from 'react';
import { I18nProvider } from 'fumadocs-ui/i18n';
export default function Layout({
params: { lang },
children,
}: {
params: { lang: string };
children: ReactNode;
}) {
return (
<html lang={lang}>
<body>
<I18nProvider
locale={lang}
// options
locales={[
{
name: 'English',
locale: 'en',
},
{
name: 'Chinese',
locale: 'cn',
},
]}
// translations
translations={
{
cn: {
toc: '目錄',
},
}[lang]
}
>
<RootProvider>{children}</RootProvider>
</I18nProvider>
</body>
</html>
);
}Code Block Usage
The previous usage was confusing, some props are passed directly to pre while some are not.
With v13, pass all props to the CodeBlock component.
This also includes class names, you may change your custom styles if necessary.
import { Pre, CodeBlock } from 'fumadocs-ui/components/codeblock';
<MDX
components={{
// HTML `ref` attribute conflicts with `forwardRef`
pre: ({ ref: _ref, ...props }) => (
<CodeBlock {...props}>
<Pre>{props.children}</Pre>
</CodeBlock>
),
}}
/>;Remove Deprecated APIs
- Remove deprecated
fumadocs-ui/components/apicomponents. - Replace
secondarylink items withiconlink items. - Rename
idprop on Tabs component togroupId.
Bug Fixes
UI
- Fixed empty folder animation problems caused by Radix UI.
Apps Docs Content Blog V14 (`apps/docs/content/blog/v14.mdx`)
title: Fumadocs v14 authors: [fuma] tags: [Release] date: 2024-09-19
Why?
Taking some inspirations from Shadcn UI, I decided to make Fumadocs easier to use, with more APIs that simplify the design.
This is a large update.
New Features
Fumadocs CLI
Clone Fumadocs UI components to your workspace and modify them.
npm install @fumadocs/cli --save-devpnpm fumadocs addSee docs for details.
Sidebar Tabs
Previously, multiple page tree is implemented with Fumadocs UI RootToggle component.
You have to add it to the sidebar banner which isn't as intuitive as other APIs.
With Sidebar Tabs, by creating a root folder, Fumadocs will immediately add a tabs component to the sidebar.
{
"root": true,
"title": "Tab Name",
"description": "Some text about the tab",
"icon": "IconName"
}Orama
We migrated the built-in search from Flexsearch to Orama, the same search engine that powers the Node.js docs site.
It is open source, and also have their Cloud integration.
No changes required.
You can use our new createFromSource API to create the route handler, which offers i18n support without any configurations.
import { source } from '@/lib/source';
import { createFromSource } from 'fumadocs-core/search/server';
export const { GET } = createFromSource(source);In favour of this, the new search client of Fumadocs is introduced with support for different API clients.
This includes Algolia, Orama (static and dynamic with route handlers).
import { useDocsSearch } from 'fumadocs-core/search/client';
import { oramaStaticClient } from 'fumadocs-core/search/client/orama-static';
const client = useDocsSearch({
client: oramaStaticClient(),
});See Search API.
Static Search
For sites with static export, you cannot use route handlers.
We now support client-side search using search indexes generated during build time.
See Static Search.
Less Dependencies
I'm always working to reduce the dependencies required for Fumadocs, that is one reason why we separated Fumadocs into different packages.
Fumadocs UI now bundles icons from lucide-react, for Next.js apps that is not using Lucide, they can avoid downloading the entire icon library.
And swr is no longer used for search client, we implemented a light useQuery hook with extra care on React performance optimization.
New Metadata Image API
To improve code organization, we made a Metadata Image API on Fumadocs Core.
It is a tiny wrapper over Next.js Metadata API, composes seamlessly with Source API.
import { createMetadataImage } from 'fumadocs-core/server';
import { source } from '@/lib/source';
export const metadataImage = createMetadataImage({
imageRoute: '/docs-og',
source,
});import { generateOGImage } from 'fumadocs-ui/og';
import { metadataImage } from '@/lib/metadata';
export const GET = metadataImage.createAPI((page) => {
return generateOGImage({
title: page.data.title,
description: page.data.description,
site: 'My App',
});
});
export function generateStaticParams() {
return metadataImage.generateParams();
}import { source } from '@/lib/source';
import { metadataImage } from '@/lib/metadata';
import { notFound } from 'next/navigation';
export function generateMetadata({ params }: { params: { slug?: string[] } }) {
const page = source.getPage(params.slug);
if (!page) notFound();
return metadataImage.withImage(page.slugs, {
title: page.data.title,
description: page.data.description,
});
}In favour of this, the getImageMeta function from fumadocs-ui/og has been removed.
Shorthands
As you may notice, we introduced more abstractions to reduce the setup steps required for enabling some features.
It is also required for the code generator from Fumadocs CLI to work.
Like the generateParams function, it enables zero-configuration i18n support for Next.js generateStaticParams.
import { source } from '@/lib/source';
export function generateStaticParams() {
return source.generateParams();
}Better Card Component
Fumadocs UI Card can now support usage without href.
You can pass children as React node, Typography styles will be applied correctly.
<Card icon={<Database />} title='Content Source'>
The input/source of your content, it can be a CMS, or local data layers like [Content Collections](https://www.content-collections.dev) and [Fumadocs MDX](/docs/mdx).
</Card>Better Category Component
The original DocsCategory component was copied from our official docs, which is a very simple implementation that doesn't take page tree into account.
Now it accepts the source object via from prop.
import { source } from '@/lib/source';
import { DocsCategory } from 'fumadocs-ui/page';
const page = source.getPage(params.slug);
<DocsCategory page={page} from={source} />;By default, it takes the locale property from page to find the corresponding page tree to traverse.
You can also force a page tree.
import { source } from '@/lib/source';
import { DocsCategory } from 'fumadocs-ui/page';
const page = source.getPage(params.slug, params.lang);
<DocsCategory page={page} from={source} tree={source.pageTree['en']} />;OpenAPI Tag Display Name
Change the display name with x-displayName.
tags:
- name: test
description: this is a tag.
x-displayName: My Test NameBetter TypeScript Docs Automation
The AutoTypeTable component now supports a type prop, you can use TypeScript inside the field:
<AutoTypeTable
path="file.ts"
name="B"
type={`
import { ReactNode } from "react"
export type B = ReactNode | { world: string }
`}
/>And code highlighting in typedoc is also supported with Shiki.
We highly recommend to use createTypeTable instead of importing the component in MDX files, this allows a single instance of TypeScript Compiler API to be shared.
See Auto Type Table.
Next.js 15
Fumadocs v14 is compatible with Next.js 15, supporting sync and async usages of dynamic APIs.
UI Improvements
Navbar Links
The navigation menu on Home layout is redesigned, with better animation and flexibility.
See the new API.
Link Styles
You can escape Tailwind CSS Typography styles for the a tag with data-card attribute.
<a data-card="">
No styles applied <code>But it does</code>
</a>Disable Theme Switch
You can disable the default theme switcher with disableThemeSwitch on layouts.
Breaking Changes
Move `dir` option from `defineDocs`
import { defineDocs } from 'fumadocs-mdx/config';
export const { docs, meta } = defineDocs({
dir: 'my/content/dir', // define once
});Refactor Imports
Previous
import { DocsLayout } from 'fumadocs-ui/layout';
import { HomeLayout } from 'fumadocs-ui/home-layout';
import { HomeLayoutProps } from 'fumadocs-ui/home-layout';New Import Path
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import { HomeLayout } from 'fumadocs-ui/layouts/home';
import { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';Twoslash UI
We moved Twoslash UI components to fumadocs-twoslash.
This isolates some logic from Fumadocs UI, allowing a better code organization.
Import the CSS styles and Popup component differently.
import 'fumadocs-twoslash/twoslash.css';
import { Popup } from 'fumadocs-twoslash/ui';Remove Deprecated APIs
The previous createI18nSearchAPIExperimental is now renamed to createI18nSearchAPI.
It takes the i18n config instead of scattering the options everywhere.
The types from fumadocs-core/search/shared is moved to fumadocs-core/server.
Apps Docs Content Blog V15 2 (`apps/docs/content/blog/v15-2.mdx`)
title: Fumadocs v15.2 date: 2025-03-28 authors: [fuma] tags: [Release]
Overview
Fumadocs 15.2 introduces a layer over fumadocs-core to extend support to other React frameworks, including React Router and Tanstack Start. Other frameworks can also be supported using the FrameworkProvider from fumadocs-core/framework/base.
Why?
The main focus of Fumadocs is flexibility, I aim to support more frameworks as long as it doesn't change the fundamentals of Fumadocs, like the separation of core and UI package.
The React.js ecosystem is a joy to work with, porting Fumadocs to other frameworks isn't as difficult as I thought.
It's also my goal to make it work not only for Next.js devs, but also everyone in the ecosystem who is using a SSR-compatible React framework.
Breaking Changes
This is a minor release, it shouldn't break any previous usages unless you're relying on the lower level APIs from fumadocs-core without fumadocs-ui.
Fumadocs Core now requires a FrameworkProvider. It's as simple as wrapping it with appropriate provider:
import { NextProvider } from 'fumadocs-core/framework/next';
export function Provider({ children }) {
return <NextProvider>{children}</NextProvider>;
}If you're using Fumadocs UI, there's no need to change. RootProvider included it by default, and allows you to provide your own framework with:
import { RootProvider } from 'fumadocs-ui/provider/base';
export function Provider({ children }) {
// now it doesn't add the Next.js provider
return <RootProvider>{children}</RootProvider>;
}Please report issues if you find any public APIs are broken unexpectedly.
Compilation Time
15.2 also included some small performance improvements to Fumadocs, the time taken for Turbopack to start dev server and compile its first docs page is about 2-3 seconds.
This change also potentially allows Fumadocs to run on Vite, production build of minimal React Router setup only takes around 4s on a Macbook.
Try it out
Upgrade from your existing Next.js docs:
pnpm update -i -rIf you want to try the React Router example:
pnpm create fumadocs-appFuture Plans
15.2 doesn't support Astro, and probably won't unless better React.js support is provided by Astro.
transition:persistis needed for Fumadocs UI layouts, however this will also affect its children (e.g. MDX content), causing page content to be persisted even after navigation.- React contexts cannot work across islands, this changes the usage of Fumadocs significantly and hence require a redesign of APIs.
Fumadocs will continue to be Next.js first, the docs won't be changed to generalize usages of other frameworks. Instead, a new docs site will be developed for other frameworks (planned).
Apps Docs Content Blog V15 (`apps/docs/content/blog/v15.mdx`)
title: Fumadocs v15 authors: [fuma] tags: [Release] date: 2025-01-24
Overview
The purpose of v15 is mainly to support Tailwind CSS v4.
Tailwind CSS v4 is a complete redesign of its API and internals, to fully adhere to the new CSS-first config style, a breaking change is required.
Fumadocs v15 has no other significant changes other than migrating the config to Tailwind CSS v4.
Breaking Changes
Before making the switch, upgrade your site to Tailwind CSS v4 following their upgrade guide.
You can remove the unused tailwind.config.js file and fully rely on the new CSS-first config.
Add @import to the presets exported by Fumadocs UI, and include the fumadocs-ui package in the source.
@import 'tailwindcss';
@import 'fumadocs-ui/css/neutral.css';
@import 'fumadocs-ui/css/preset.css';
/* relative to the CSS file, make sure it's correct for your app */
@source '../node_modules/fumadocs-ui/dist/**/*.js';Since v15, you will no longer pass options in JavaScript to customize plugins.
Instead, you can use the new alternatives in Tailwind CSS v4.
`addGlobalColors: true`
Forward the colors again:
@theme {
--color-primary: var(--color-fd-primary);
/* same for other colors */
}CSS Variables
Fumadocs no longer use --fd-<color> like --fd-primary: 0 0% 0% for colors, it directly defines and uses colors in @theme.
Instead of the previous format, it uses hsl():
@theme {
--color-fd-primary: hsl(0, 0%, 100%);
}Steps
Previous Tailwind CSS utilities like steps & step are renamed to fd-steps and fd-step to avoid conflicts.
Typography
Typography utilities including prose, prose-* modifiers will continue to work. Please report problems if they are no longer working or have unexpected change in behaviours.
Improvements
v15 also includes some minor improvements to the UI and OpenAPI integration.
Code Block Tabs
In the past, you needed to write the <Tabs /> manually when separating code blocks into tabs.
<Tabs items={["Tab 1", "Tab 2"]}>
```ts tab="Tab 1"
console.log('Hello World');
```
```ts tab="Tab 2"
console.log('Hello World');
```
</Tabs>Now you can do:
```ts tab="Tab 1"
console.log('Hello World');
```
```ts tab="Tab 2"
console.log('Hello World');
```Note that the previous usage still works for those who want to customize or pass props to the <Tabs /> component.
Reversed Items in `meta.json`
The rest item ... in the pages property of meta.json now supports reversed order:
{
"pages": ["z...a"]
}OpenAPI Playground
v15 improves the playground UI (inspired by the minimalism of Scalar), and brought Scalar API Client support to Fumadocs OpenAPI.
You can enable the Scalar API Client using useScalar option in createOpenAPI():
import { createOpenAPI } from 'fumadocs-openapi/server';
import { APIPlayground } from 'fumadocs-openapi/scalar';
export const openapi = createOpenAPI({
renderer: {
APIPlayground,
},
});And install & configure their @scalar/api-client-react:
@scalar/api-client-react@import '@scalar/api-client-react/style.css' layer(base);Future Plans
This update should be a simple migration for most developers updating to Tailwind CSS v4.
In the future, we also want to make further improvements to Fumadocs MDX:
- remove
transformAPI - you can easily do the same with.map()on runtime like:
import { blog } from "@/.source"
export const updatedBlog = blog.map(...)remove Manifest API - it was designed to export search indexes, but now it's recommended to implement using route handlers.
Mention more about our MDX Remote package, it will be the primary solution to handle large docs sites with performance needs that bundlers couldn't do, including lazy compilation of MDX files.
Apps Docs Content Blog V16 17 (`apps/docs/content/blog/v16-17.mdx`)
title: Fumadocs 16.17 authors: [fuma] tags: [Release] date: 2026-10-09 ogImage: /blog/v16-17.png
import { MarkdownLayer } from './v16-17/layer';
import { Pipeline } from './v16-17/pipeline';
import { RenderToMarkdown } from './v16-17/render';
import { TableRecords } from './v16-17/search';
import { PerfChart } from './v16-17/perf';
We are pleased to announce the release of Fumadocs 16.17.
This release rethinks how Fumadocs turns your pages into Markdown, and builds on top of it:
- A new stringification pipeline: Markdown that gives humans and agents the right context.
- Better search: records that keep the structure of your content.
- Better performance: Markdown and search records are generated over 10x faster.
The Markdown Form of a Page
A page is read in more places than a browser. Agents fetch its Markdown from .md routes, llms.txt and MCP servers, readers copy it into ChatGPT, and your search indexes it.
In Fumadocs, Remark LLMs generates the Markdown, and Remark Structure generates the search records.
For these readers, the Markdown is the page. It should be informative: the content you wrote, not how we render it.
16.17 rethinks the layer that generates it.
The Old Pipeline
Both plugins used to stringify the syntax tree, after the other remark plugins. By then, the tree was already transformed for rendering:
This is what 16.16 generated from a page of 9 lines:
## Installation
```npm
npm i fumadocs-core
```
<auto-type-table path="./props.ts" name="Options" />## Installation [#installation]
<CodeBlockTabs defaultValue="npm">
<CodeBlockTabsList>
<CodeBlockTabsTrigger value="npm">
npm
</CodeBlockTabsTrigger>
<CodeBlockTabsTrigger value="pnpm">
pnpm
</CodeBlockTabsTrigger>
<CodeBlockTabsTrigger value="yarn">
yarn
</CodeBlockTabsTrigger>
<CodeBlockTabsTrigger value="bun">
bun
</CodeBlockTabsTrigger>
</CodeBlockTabsList>
<CodeBlockTab value="npm">
```bash
npm i fumadocs-core
```
</CodeBlockTab>
<CodeBlockTab value="pnpm">
```bash
pnpm add fumadocs-core
```
</CodeBlockTab>
<CodeBlockTab value="yarn">
```bash
yarn add fumadocs-core
```
</CodeBlockTab>
<CodeBlockTab value="bun">
```bash
bun add fumadocs-core
```
</CodeBlockTab>
</CodeBlockTabs>
<img alt="Preview" src="__img0" />
<TypeTable
id="type-table-props.ts-Options"
type="{
"id": "props.ts-Options",
"name": "Options",
"description": "",
"entries": [
{
"name": "dir",
"description": "The directory of pages.",
"tags": [],
"type": "string",
"simplifiedType": "string",
"required": true,
"deprecated": false
},
{
"name": "baseUrl",
"description": "The base URL of pages.",
"tags": [
{
"name": "defaultValue",
"text": "'/'"
}
],
"type": "string | undefined",
"simplifiedType": "string",
"required": false,
"deprecated": false
}
]
}"
/>- Generated elements pollute the Markdown.
<CodeBlockTabs>and `` are how we render the page, not your content.__img0doesn't even exist outside the bundle. - Components are not rendered. A JSX element is only its tag and props, its content lives in the React component. The type table became a JSON blob.
- Your Markdown is reformatted.
-lists become*,_emphasis_becomes*emphasis*, and2 * 3is escaped into2 \* 3. - It is slow. The whole tree is serialized for every page, and again for every search record.
We fixed it in two parts.
Components, Rendered
Since 16.15, fumadocs-core/server renders React Server Components into Markdown. A component opts in by calling asMarkdown(), and returns its Markdown form:
import { asMarkdown, md } from 'fumadocs-core/server';
export function Callout({ title, children }) {
if (asMarkdown()) return md.linePrefix('> ')`**${title}**\n${children}`;
return <div className="callout">...</div>;
}With output: 'function', Remark LLMs exports the Markdown as a component instead of a string. Markdown stays as you wrote it, JSX elements are rendered by your MDX components:
import { defineDocs } from 'fumadocs-mdx/config';
export const docs = defineDocs({
docs: {
postprocess: {
includeProcessedMarkdown: { output: 'function' },
},
},
});import { getMDXComponents } from '@/components/mdx';
const markdown = await page.data.getText('processed', {
components: getMDXComponents(),
});Components that never call asMarkdown(), including client components, are kept as JSX with their props. See Markdown Rendering.
Source + Edits
The other part is where the Markdown comes from.
In 16.17, it is sliced from your source instead of stringified from the tree. Every remark plugin maintains two pipelines:
- the syntax tree, transformed for rendering.
- edits to your source, for the generated Markdown.
The rules are simple:
- A node with a position outputs its source.
- A node inserted by plugins outputs nothing, the plugin describes its Markdown instead.
The same page, in 16.17:
## Installation [#installation]
```npm
npm i fumadocs-core
```
### Options
| Prop | Type | Description |
| ---------- | -------- | ------------------------------------- |
| `dir` | `string` | The directory of pages. |
| `baseUrl?` | `string` | The base URL of pages. Default: `'/'` |Plugins record their edits with replaceSource():
import type { Root } from 'mdast';
import type { VFile } from 'vfile';
import { visit } from 'unist-util-visit';
import { replaceSource } from 'fumadocs-core/mdx-plugins/stringifier';
export function remarkNote() {
return (tree: Root, file: VFile) => {
visit(tree, 'mdxJsxFlowElement', (node) => {
if (node.name !== 'Note') return;
replaceSource(file, node, (s) => `**Note:**\n\n${s.inner(node)}`);
// transform the node for rendering...
});
};
}embedSource()embeds content parsed from another source, like included files.- A namespace limits an edit to some outputs, like
searchfor search records.
It brings:
- Quality: Markdown stays as you wrote it, local images keep their addresses, and generated nodes leave no noise.
- Performance: no tree is serialized, Remark LLMs is up to 45x faster.
The Sätteri compiler has worked this way since @fumadocs/satteri 0.5, now the remark plugins of Fumadocs Core behave the same. See Stringification for details.
Better Search
Search records come from the same pipeline, so they are Markdown as you wrote it. The search dialog renders them with typography, and elements like <Callout /> and <TypeTable /> as components.
Tables, by Row
A table records each row, as a table of its header and that row. Type tables from auto-type-table record a row for each prop. In the search dialog, the matched rows of a table are grouped back into a table.
Highlights with CSS
Search results no longer wrap matches in <mark>, the content stays as indexed. The search dialog highlights matches with the CSS Custom Highlight API:
::highlight(fd-search) {
color: var(--color-fd-primary);
text-decoration: underline;
}Custom search UIs can use useHighlightQuery() from fumadocs-core/search/client.
Search Hooks
Each search client has its own hook, like useFetchSearch() and useAlgoliaSearch(), returning the props of <SearchDialog />:
import { useFetchSearch } from 'fumadocs-core/search/client';
const search = useFetchSearch();
<SearchDialog {...search} {...props} />;useDocsSearch() is deprecated, see Search.
Mixedbread
sync() from fumadocs-core/search/mixedbread uploads a file for each page, whose chunks are its title, headings and paragraphs. Results link to their headings, and render tables like other integrations.
See Mixedbread.
Better Performance
| Document | Remark LLMs | Remark Structure | Default plugins |
|---|---|---|---|
| 0.5 MB | 49 → 1.1 ms | 49 → 2.4 ms | 119 → 9.4 ms |
| 2 MB | 184 → 4.1 ms | 196 → 11.1 ms | 482 → 34.1 ms |
| 5 MB | 498 → 14.1 ms | 594 → 30.3 ms | 1,496 → 87.5 ms |
Default plugins are the remark plugins of Fumadocs MDX, with Remark LLMs enabled.
- The remark and rehype plugins of Fumadocs Core no longer slow down quadratically on large documents, like
rehypeToc()taking 17 ms instead of 239 ms on a 5 MB page. - The search dialog parses results with micromark and the HTML parser of browsers, instead of a remark & rehype pipeline.
- Twoslash analyzes code blocks about 2x faster, and truncates types longer than 500 characters in popups. Add
.twoslashto your.gitignore.
Refactored & Deprecated APIs
16.17 is a minor release. Re-sync your search indexes to pick up the new records, including Mixedbread stores synced with mxbai store sync.
The internal APIs of stringification and search are refactored, for plugins and search UIs built on them:
| Before | Now |
|---|---|
data._stringify |
replaceSource() & embedSource() from fumadocs-core/mdx-plugins/stringifier |
defaultStringifier() |
createStringifier() |
placeholder() |
the mdxAsPlaceholder option |
stringify & allowedMdxAttributes of Remark plugins |
filterElement, and replaceSource() for custom Markdown |
useSearchList() |
getActive(), setActive() & subscribeActive() from useSearch() |
contentWithHighlights & renderHighlights |
useHighlightQuery() |
- Remark Structure defaults
typestotableinstead oftableCell. <SearchDialogListItem />renders adiv, and itsrenderMarkdownreceivescontent.fumadocs-mdx,fumadocs-docgenandfumadocs-obsidianrequirefumadocs-core16.17.0.
Deprecated APIs keep working:
useDocsSearch(): use the hook of your search client.createContentHighlighter(): matches are highlighted with CSS.
Also in 16.17
- Fumadocs OpenAPI 12.4: auth providers for the API playground (
playground.authProviders), the OAuth redirect URI with a copy button, and fixed code samples in every language. index()andindexNode()ofllms()return a string again when it receives a loader.- The advanced search server returns up to 60 results when the request has no
limit.
Thanks for supporting Fumadocs, share your upgrade experience on GitHub :)
Apps Docs Content Blog V16 2 (`apps/docs/content/blog/v16-2.mdx`)
title: Fumadocs 16.2 authors: [fuma] tags: [Release] date: 2025-11-29
We are pleased to announce the release of Fumadocs 16.2, introducing a better layout system for Fumadocs UI.
The New Layout System
Handling layouts in documentation frameworks can present significant challenges.
As a solution, Fumadocs UI implemented a Grid System that is composable, flexible, cohesive and predictable.
See the linked docs for more details.
In short, the layout consists of a grid container, and layout components using position: sticky:
#nd-docs-layout {
grid-template:
'sidebar header toc'
'sidebar toc-popover toc'
'sidebar main toc' 1fr / minmax(var(--fd-sidebar-col), 1fr) minmax(0, var(--fd-page-col))
minmax(min-content, 1fr);
--fd-docs-row-1: var(--fd-banner-height, 0px);
--fd-docs-row-2: calc(var(--fd-docs-row-1) + var(--fd-header-height));
--fd-docs-row-3: calc(var(--fd-docs-row-2) + var(--fd-toc-popover-height));
}The container defines the offsets of each row, layout components can leverage them according to their grid area:
.layout-component {
position: sticky;
top: var(--fd-docs-row-1);
height: calc(var(--fd-docs-height) - var(--fd-docs-row-1));
}Layout components are also responsible to declare their heights via CSS variables, allowing container to calculate the right row offsets.
#nd-docs-layout:has(.layout-component) {
--fd-header-height: 56px;
}For user-land customizations, we recommend the Fumadocs CLI customize command, or hooking the relevant grid areas like:
.custom-component {
/* depends on the layout, usually we have toc, toc-popover, sidebar, header, main */
grid-area: main;
}Why the Change?
Previously, Fumadocs UI relied on position: fixed and mathematical computations (inspired by Vitepress). The older method often lacked composability and difficult for beginners to manage.
The new system fixed most of the flaws, allowing easier visualizations, while reducing the complexity of previous solutions.
Breaking Changes
Global Styles
While Fumadocs 16.2 preserves compatibility in several areas, notably:
- The
--fd-layout-widthCSS variable continues to function as expected. - All ID and data attributes remain unchanged.
However, the shift to the new grid system may impact existing customizations, particularly those dependent on the prior fixed-position logic. We recommend reviewing your implementation carefully and testing thoroughly.
If you have already installed layout components via Fumadocs CLI, replace preset.css with preset-legacy.css to keep legacy styles & avoid unexpected breakages:
/* [!code --] */
@import 'fumadocs-ui/css/preset.css';
/* [!code ++] */
@import 'fumadocs-ui/css/preset-legacy.css';No Longer Exposing Internal Components
Layout components such as Root Toggle, Language Toggle, and Theme Toggle are no longer directly exposed.
This decision enables Fumadocs UI to iterate these elements without introducing unexpected breaking changes to usage outside of API surfaces.
Migration Steps: Override the relevant layout components with your own implementations, or utilize the Fumadocs CLI commands (add or customize) to install them locally.
Removal of Internal Contexts
The following context modules are made private:
fumadocs-ui/contexts/sidebarfumadocs-ui/contexts/layout
If you have already installed <DocsLayout /> via Fumadocs CLI, import the legacy contexts instead:
// [!code --:2]
import {} from 'fumadocs-ui/contexts/sidebar';
import {} from 'fumadocs-ui/contexts/layout';
// [!code ++:2]
import {} from 'fumadocs-ui/legacy/sidebar';
import {} from 'fumadocs-ui/legacy/layout';And wrap your app under the legacy <SidebarProvider />:
import type { ReactNode } from 'react';
import { SidebarProvider } from 'fumadocs-ui/legacy/sidebar';
export function RootLayout({ children }: { children: ReactNode }) {
return <SidebarProvider>{children}</SidebarProvider>;
}The legacy contexts are made exclusively for layout components installed locally. If you still
import layout components (e.g. <DocsLayout />) from fumadocs-ui, migrate your usages as follows:
// [!code --]
import { useSidebar } from 'fumadocs-ui/contexts/sidebar';
// [!code ++]
import { useSidebar } from 'fumadocs-ui/components/sidebar/base';Ensure the contexts are only accessed within <DocsLayout />.
Minor Changes
The changes below are backward compatible.
Importing Page Layouts by Type
The notebook layout is now isolated from default layout, including their page components.
Make sure to import the appropriate page layout based on your docs layout:
// For docs layout
import { DocsPage } from 'fumadocs-ui/layouts/docs/page';
// For notebook layout
import { DocsPage } from 'fumadocs-ui/layouts/notebook/page';Migration Steps: Update your imports accordingly. Although the default fumadocs-ui/page will redirect to the correct layout, we strongly advise explicit imports for clarity and future-proofing.
Removal of Unused Styles
Styles related to the abandoned container utility, and --spacing-fd-container variable, have been dropped as they are no longer utilized.
Migration Steps: Remove references to these styles from your codebase.
Conclusion
Fumadocs 16.2 aims to offer greater flexibility for developers. Welcome to share your thoughts about the new design on GitHub Discussion!
Apps Docs Content Blog V16 (`apps/docs/content/blog/v16.mdx`)
title: Fumadocs v16 authors: [fuma] tags: [Release] date: 2025-10-22
Overview
We are pleased to announce the release of Fumadocs v16, a significant update that introduces breaking changes designed to refine our API surface, improve compatibility with Vite frameworks, and boost overall performance.
Major Changes
Fumadocs 16 removed substantial amount of deprecated APIs accumulated since v15.2 Vite support.
Removal of the Sidebar API: The
fumadocs-core/sidebarmodule has been deprecated and removed. For sidebar implementations, we recommend adopting pre-built solutions from Shadcn UI.Restructuring of Server-Side Exports: Exports from
fumadocs-core/serverhave been redistributed to more specialized modules:getGithubLastEdit->fumadocs-core/content/github.getTableOfContents->fumadocs-core/content/toc.PageTreeand related utilities ->fumadocs-core/page-tree.TOCItemTypeandTableOfContents->fumadocs-core/toc.- removed
createMetadataImageAPI, use Next.js Metadata API instead.
Default Switch to Shiki's JavaScript Regex Engine: To ensure seamless compatibility with environments like Cloudflare Workers, the JavaScript engine is now the default over the WASM-based Oniguruma. This change affects
rehype-code(via theengineoption) andfumadocs-core/highlight.Minimum React.js Version Raised to 19.2.0: React 19.2 brings crucial performance improvements to Fumadocs UI.
- Next.js Projects: Next.js projects using Fumadocs UI must use Next.js 16 or later, as Next.js uses its own canary channel for React.js.
fumadocs-ui/provideris removed:- Use
fumadocs-ui/provider/nextfor root provider. - Use individual exports for contexts from
fumadocs-ui/contexts/*.
- Use
fumadocs-ui/page:Removed
<DocsCategory />, usegetPageTreePeers()instead:tsx title="page.tsx"import { getPageTreePeers } from 'fumadocs-core/page-tree'; import { source } from '@/lib/source'; <Cards> {getPageTreePeers(source.getPageTree(), '/docs/my-page').map((peer) => ( <Card key={peer.url} title={peer.name} href={peer.url}> {peer.description} </Card> ))} </Cards>;Removed the
breadcrumbs.fulloption in<DocsPage />.
fumadocs-core/search/algolia: Thedocumentoption is renamed toindexName.fumadocs-core/search: Removed the deprecatedcreateFromSource()signature inuseSearch().fumadocs-core/highlight: RemovedwithPrerenderScriptandloadingoptions fromuseShiki(), use React Suspense API instead.fumadocs-core/i18n: RemovedcreateI18nMiddleware(import fromfumadocs-core/i18n/middlewareinstead).fumadocs-core/source:- Removed
transformers,pageTree.attach*options inloader(): use Loader Plugin instead. - Removed the
page.fileproperty: usepage.pathinstead. - Removed internal utilities
FileInfoandparseFilePath: usePathUtilsinstead.
- Removed
Migration to Orama Cloud's New SDK: The
@orama/corepackage replaces the prior client. Update your synchronization code as follows:tsimport { sync } from 'fumadocs-core/search/orama-cloud'; import { OramaCloud } from '@orama/core'; // [!code highlight:4] const orama = new OramaCloud({ projectId: '<project id>', apiKey: '<private api key>', }); await sync(orama, { index: '<data source id>', documents: records, });You can also continue to use the legacy Orama Cloud client:
ts// [!code --] import { oramaCloudClient } from 'fumadocs-core/search/client/orama-cloud'; // [!code ++] import { oramaCloudLegacyClient } from 'fumadocs-core/search/client/orama-cloud-legacy'; useSearch({ // [!code --] client: oramaCloudClient(...), // [!code ++] client: oramaCloudLegacyClient(...), })Refer to the Orama Cloud documentation for further details.
Styling Changes
Fumadocs UI has included some aggressive styling changes that you should know about.
The default
--fd-layout-widthCSS variable is set to1600pxfor optimal content scaling.
You can override it if needed.css:root { --fd-layout-width: 100vw; }
Fumadocs OpenAPI 9.6
A redesigned schema UI emphasizes client-side rendering (CSR) for recursive components, reducing bundle sizes and enhancing performance in complex JSON schemas.
Non-required fields now include a reset button, with support for content.showExampleInFields.
What's Next?
Fumadocs v16 is a new start for Fumadocs. We appreciate your feedback and contributions—please share your experiences on GitHub. Thank you for supporting Fumadocs :)
Apps Docs Content Blog Why Docs (`apps/docs/content/blog/why-docs.mdx`)
title: Why do you need docs? date: 2024-05-26 authors: [fuma]
People often ask me, do we really need a framework to build docs sites? Well, You don't.
Documentation sites are so important in software development,
internal docs for developers in your company to understand the architecture,
docs for frameworks,
docs for web standards...
Building docs seems simple, but can be difficult.
There are so many paradigms to build docs, but writing beginner-friendly docs could be difficult.
As a result, people tend to use powerful docs frameworks, making the docs site interactive and straightforward.
Over-Engineered
For the docs of a small library/API service, you probably don't need to setup a Next.js site and spend time writing your site.
Neither Nextra nor Fumadocs could be better than GitHub wiki and Swagger docs in this case.
They offer a good, decent UI, basic functionalities, and a proper workflow of authoring docs.
The DX is good enough, I can't think of a reason to switch to a full-powered docs framework just to make your docs look fancy.
I'll just recommend writing your docs in markdown, make it accessible on your GitHub repository.
Why Framework?
Now you may wonder, why major services and frameworks have their own docs sites built with docs frameworks?
Of course, Usually using things like GitHub Wiki is adequate, but it is not always true.
Let's take Component Library for example, you cannot showcase your components with Markdown.
You will constantly find an ordinary Markdown document lacks capability and flexibility.
Documentation frameworks aim to solve this problem, with the ability to integrate with major libraries like React.js and Vue.js.
Good examples are Vitepress, Mintlify and Nextra - They made writing docs more convenient and effective, while offering a better, dedicated experience to readers.
For anything more than a simple library or API service, it is worth trying.
Reinvent the Wheels
I would never recommend building a "custom docs site" on your own, without a proper docs framework.
Despite the Don't re-invent the wheels principle, your hand-made docs site actually takes way more effort to make it decent.
- Document Search
- A user-friendly navigation experience
- Reading experience
- UI/UX Design
Implementing them properly already sounds nerve-racking, right?
The docs itself, is definitely not your first priority. You should never spend your precious time re-inventing the wheels - it isn't worth it.
From my perspective, I'd rather use GitHub Wiki than re-inventing the wheels.
Why don't pick a decent solution? It saves your indispensable time, and help reduce the shitty docs sites on the internet.
What do we focus at Fumadocs?
I personally value reading experience more than a fancy eye-catching UI.
You may notice, we do not have animations everywhere, and we avoided many fancy designs.
Fanciness of UI should stay only in landing page, a docs site should focus on content.
Navigation elements are helpers to browse your site, never should they take up too much space on the screen.
One thing I hated the most is the design of two sidebars, it is confusing and meaningless.
You can just organize all items to a single, clean sidebar, but people instead added two hamburger buttons to the navbar.
My favourite docs site is still Linear docs, looks good and simple.
Conclusion
- You don't need a full-powered docs framework for a small library
- Don't make a docs site on your own, use a proper docs framework
- Fumadocs focuses on reading experience
- You should focus on content, too
Examples Astro Content Docs Test (`examples/astro/content/docs/test.mdx`)
title: Components
Code Block
console.log('Hello World');Cards
Examples Asyncapi Content Docs Auth (`examples/asyncapi/content/docs/auth.mdx`)
title: Authentication
Security requirements are defined per operation in the AsyncAPI document. Supported schemes depend on the protocol and server configuration.
Security Schemes
Check the Authorization section on each operation page for the applicable security schemes and scopes.
When connecting to a broker or gateway, configure credentials according to your environment (for example API keys, SASL, or TLS client certificates).
Examples Base Ui Content Docs Test (`examples/base-ui/content/docs/test.mdx`)
title: Components
Code Block
console.log('Hello World');Cards
Examples Content Collections Content Docs Test (`examples/content-collections/content/docs/test.mdx`)
title: Hello World
Hey there!
Heading
Heading
console.log('Hello World');Heading
Examples I18n Content Docs Test (`examples/i18n/content/docs/test.mdx`)
title: Hello World
Hey there!
Heading
Heading
console.log('Hello World');Heading
Examples Next Local Md Content Docs Test (`examples/next-local-md/content/docs/test.mdx`)
title: Components
Code Block
console.log('Hello World');Cards
no
Examples Next Min Content Docs Test (`examples/next-min/content/docs/test.mdx`)
title: Test
Hey there!
Cards
CodeBlock
console.log('Hello World');List
- Hello
- World
Examples Next Min Content Docs Test2 (`examples/next-min/content/docs/test2.mdx`)
title: Fumadocs
Overview
Fumadocs is a docs framework.
Examples Next Static Content Docs Test (`examples/next-static/content/docs/test.mdx`)
title: Components
Code Block
console.log('Hello World');Cards
Examples Next Content Docs Test (`examples/next/content/docs/test.mdx`)
title: Components
Code Block
console.log('Hello World');Cards
Examples Openapi Content Docs Auth (`examples/openapi/content/docs/auth.mdx`)
title: 'Authentication'
You'll need to authenticate your requests to access some of the endpoints in the Unkey API. In this guide, we'll look at how authentication works.
Bearer Token
When requesting resources, you will need your root key — you will find it in the Dashboard. Here's how to add the root key to the request header using cURL:
curl https://api.unkey.dev/v1/... \
-H "Authorization: Bearer unkey_xxx"Always keep your root key safe and reset it if you suspect it has been compromised.
--- METRICS ---
- Files Extracted: 99
- Estimated Token Budget: ~60203 tokens
- Canonical Reference: https://codewiki.google/github.com/fuma-nama/fumadocs