# Fumadocs CLI (the CLI tool for automating Fumadocs apps): create-fumadocs-app URL: /docs/cli/create-fumadocs-app Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/cli/create-fumadocs-app.mdx The CLI to create new Fumadocs apps ## Overview Despite the classical CLI usage, you can also use it in scripts like: npm pnpm yarn bun ```bash npm install create-fumadocs-app ``` ```bash pnpm add create-fumadocs-app ``` ```bash yarn add create-fumadocs-app ``` ```bash bun add create-fumadocs-app ``` ```ts import { create } from 'create-fumadocs-app'; await create({ outputDir: 'my-app', template: '+next+fuma-docs-mdx', packageManager: 'pnpm', }); ``` # Fumadocs CLI (the CLI tool for automating Fumadocs apps): User Guide URL: /docs/cli Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/cli/index.mdx The CLI tool that automates setups and installs components. ## Installation Initialize a config for CLI: npm pnpm yarn bun ```bash npx @fumadocs/cli ``` ```bash pnpm dlx @fumadocs/cli ``` ```bash yarn dlx @fumadocs/cli ``` ```bash bun x @fumadocs/cli ``` You can change the output paths of components in the config. ### Components Select and install components. npm pnpm yarn bun ```bash npx @fumadocs/cli add ``` ```bash pnpm dlx @fumadocs/cli add ``` ```bash yarn dlx @fumadocs/cli add ``` ```bash bun x @fumadocs/cli add ``` You can pass component names directly. npm pnpm yarn bun ```bash npx @fumadocs/cli add banner files ``` ```bash pnpm dlx @fumadocs/cli add banner files ``` ```bash yarn dlx @fumadocs/cli add banner files ``` ```bash bun x @fumadocs/cli add banner files ``` #### How the magic works? The CLI fetches the latest version of component from the GitHub repository of Fumadocs. 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. ### Customise A simple way to customise Fumadocs layouts. npm pnpm yarn bun ```bash npx @fumadocs/cli customise ``` ```bash pnpm dlx @fumadocs/cli customise ``` ```bash yarn dlx @fumadocs/cli customise ``` ```bash bun x @fumadocs/cli customise ``` ### Tree Generate files tree for Fumadocs UI `Files` component, using the `tree` command from your terminal. npm pnpm yarn bun ```bash npx @fumadocs/cli tree ./my-dir ./output.tsx ``` ```bash pnpm dlx @fumadocs/cli tree ./my-dir ./output.tsx ``` ```bash yarn dlx @fumadocs/cli tree ./my-dir ./output.tsx ``` ```bash bun x @fumadocs/cli tree ./my-dir ./output.tsx ``` You can output MDX files too: npm pnpm yarn bun ```bash npx @fumadocs/cli tree ./my-dir ./output.mdx ``` ```bash pnpm dlx @fumadocs/cli tree ./my-dir ./output.mdx ``` ```bash yarn dlx @fumadocs/cli tree ./my-dir ./output.mdx ``` ```bash bun x @fumadocs/cli tree ./my-dir ./output.mdx ``` See help for further details: npm pnpm yarn bun ```bash npx @fumadocs/cli tree -h ``` ```bash pnpm dlx @fumadocs/cli tree -h ``` ```bash yarn dlx @fumadocs/cli tree -h ``` ```bash bun x @fumadocs/cli tree -h ``` #### Example Output ```tsx title="output.tsx" import { File, Folder, Files } from 'fumadocs-ui/components/files'; export default ( ); ``` # Fumadocs Core (core library of framework): Custom Source URL: /docs/headless/custom-source Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/custom-source.mdx Build your own 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. * [BaseHub](https://github.com/fuma-nama/fumadocs-basehub) * [Sanity](https://github.com/fuma-nama/fumadocs-sanity) * [Community: Payload CMS by `MFarabi619`](https://github.com/MFarabi619/fumadocs-payloadcms) * [Community: Payload CMS by `bapspatil`](https://github.com/bapspatil/fumadocs-payload-template) ## Building Content Source For a custom content source implementation, there's two ways: ### Using Loader API Loader API has a file-system-like interface for integrating different content sources. See [Loader API `source`](/docs/headless/source-api/source) for details. ### Using Low-Level API For more robust control, you can use lower level APIs directly.
#### Page Tree You can either hardcode the page tree, or write some code to generate one. See [Definitions of Page Tree](/docs/headless/page-tree). Pass your page tree to `DocsLayout` (usually in a `layout.tsx`): ```tsx title="layout.tsx" import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return ( {children} ); } ``` The page tree is like a smarter "sidebar items", they will be referenced everywhere in the UI for navigation elements, such as the page footer.
#### Page Content For docs page, it's the same logic: * Define path params (`slugs`). * Fetch page content from path params. * Render the content (inside the `DocsPage` component). You may need table of contents, which can be generated on your own, or using the [`getTableOfContents`](/docs/headless/utils/get-toc) utility (Markdown/MDX only). ```tsx import { DocsPage, DocsBody } from 'fumadocs-ui/page'; import { getPage } from './my-content-source'; import { notFound } from 'next/navigation'; export default function Page({ params }: { params: { slug?: string[] } }) { const page = getPage(params.slug); if (!page) notFound(); return ( {page.render()} ); } ```
#### Pre-rendering The mechanism for pre-rendering differs depending on your React.js framework. It's important to pre-render pages (especially the frequently visited ones) to improve initial load time. Define the [`generateStaticParams`](https://nextjs.org/docs/app/api-reference/functions/generate-static-params) function to populate dynamic & catch-all routes. When pre-rendering pages from a remote source, make sure to optimize repeated reads during the build phase. For example, if you're fetching content via GitHub API, it is better to clone the repository content locally for pre-rendering. Before After ```text /docs/* -> GET github.com /docs/introduction -> GET github.com /docs/my-page -> GET github.com Error: API Ratelimit ``` ```text start -> git clone /docs/* -> ls /docs/introduction -> read file /docs/my-page -> read file ```
#### Document Search This can be difficult considering your content may not be necessarily Markdown/MDX. For Markdown and MDX, the built-in [Search API](/docs/headless/search/orama) + search index usage is adequate for most use cases. Otherwise, you will have to bring your own implementation. We recommend 3rd party solutions like Orama or Algolia Search. They are more flexible than the built-in Search API, and is easier to integrate with remote sources. Fumadocs offers a variant of [Search Integrations](/docs/headless/search).
## MDX Remote Fumadocs offers the **MDX Remote** package, it is a helper to integrate Markdown-based content sources with Fumadocs. You can think it as a `next-mdx-remote` with built-in plugins for Fumadocs. ### Setup npm pnpm yarn bun ```bash npm install @fumadocs/mdx-remote ``` ```bash pnpm add @fumadocs/mdx-remote ``` ```bash yarn add @fumadocs/mdx-remote ``` ```bash bun add @fumadocs/mdx-remote ``` The main feature it offers is the MDX Compiler, it can compile MDX content to JSX nodes. Since it doesn't use a bundler, there's some limitations: * No imports and exports in MDX files. It's compatible with Server Components. For example: ```tsx import { createCompiler } from '@fumadocs/mdx-remote'; import { getPage } from './my-content-source'; import { DocsBody, DocsPage } from 'fumadocs-ui/page'; import { getMDXComponents } from '@/mdx-components'; const compiler = createCompiler({ // options }); export default async function Page({ params, }: { params: { slug?: string[] }; }) { const page = getPage(params.slug); const compiled = await compiler.compile({ source: page.content, }); const MdxContent = compiled.body; return ( ); } ``` ### Images On serverless platforms like Vercel, the original `public` folder (including static assets like images) will be removed after production build. The MDX compiler might no longer be able to access local images in `public`. When referencing images, make sure to use a URL. # Fumadocs Core (core library of framework): Introduction URL: /docs/headless Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/index.mdx Getting started with core library ## What is this? Fumadocs Core offers server-side functions and headless components to build docs on any React.js frameworks like Next.js. * Search (built-in: Orama, Algolia Search) * Breadcrumb, Sidebar, TOC Components * Remark/Rehype Plugins * 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 No other dependencies required. npm pnpm yarn bun ```bash npm install fumadocs-core ``` ```bash pnpm add fumadocs-core ``` ```bash yarn add fumadocs-core ``` ```bash bun add fumadocs-core ``` For some components, a framework provider is needed: Next.js React Router Tanstack Start/Router Waku ```tsx 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 `` return {children}; } ``` ```tsx import type { ReactNode } from 'react'; import { ReactRouterProvider } from 'fumadocs-core/framework/react-router'; export function Root({ children }: { children: ReactNode }) { return {children}; } ``` ```tsx import type { ReactNode } from 'react'; import { TanstackProvider } from 'fumadocs-core/framework/tanstack'; export function Root({ children }: { children: ReactNode }) { return {children}; } ``` ```tsx import type { ReactNode } from 'react'; import { WakuProvider } from 'fumadocs-core/framework/waku'; export function Root({ children }: { children: ReactNode }) { return {children}; } ``` It offers simple document searching as well as components for building a good docs. # Fumadocs Core (core library of framework): Internationalization URL: /docs/headless/internationalization Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/internationalization.mdx Support multiple languages in your documentation ## Define Config Fumadocs core provides necessary middleware and options for i18n support. You can define a config to share between utilities. ```ts title="lib/i18n.ts" import { defineI18n } from 'fumadocs-core/i18n'; export const i18n = defineI18n({ defaultLanguage: 'en', languages: ['en', 'cn'], }); ``` ### Hide Locale Prefix To hide the locale prefix (e.g. `/en/page` -> `/page`), use the `hideLocale` option. ```ts import { defineI18n } from 'fumadocs-core/i18n'; export const i18n = defineI18n({ defaultLanguage: 'en', languages: ['en', 'cn'], hideLocale: 'default-locale', }); ``` | Mode | Description | | ---------------- | -------------------------------------------------- | | `always` | Always hide the prefix, detect locale from cookies | | `default-locale` | Only hide the default locale | | `never` | Never hide the prefix (default) | Using always}> On `always` mode, locale is stored as a cookie (set by the middleware), which isn't optimal for static sites. This may cause undesired cache problems, and need to pay extra attention on SEO to ensure search engines can index your pages correctly. ### Fallback Language The fallback language to use when translations are missing for a page, default to your `defaultLanguage`. Supported: * A language in your `languages` array. * When set to `null`, no fallback will be used. ```ts import { defineI18n } from 'fumadocs-core/i18n'; export const i18n = defineI18n({ languages: ['en', 'cn'], defaultLanguage: 'en', fallbackLanguage: 'cn', }); ``` ## Middleware Redirects users to appropriate locale, it can be customised from `i18n.ts`. ```ts title="proxy.ts" import { createI18nMiddleware } from 'fumadocs-core/i18n/middleware'; import { i18n } from '@/lib/i18n'; export default createI18nMiddleware(i18n); export const config = { // Matcher ignoring `/_next/` and `/api/` // You may need to adjust it to ignore static assets in `/public` folder matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], }; ``` > When `hideLocale` is enabled, it uses `NextResponse.rewrite` to hide locale prefixes. # Fumadocs Core (core library of framework): Page Slugs & Page Tree URL: /docs/headless/page-conventions Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/page-conventions.mdx Customise generated page slugs and page tree. > This guide only applies for content sources that uses `loader()` API, such as **Fumadocs MDX**. ## Overview Fumadocs generates **page slugs** and **page tree** (sidebar items) from your content directory using [`loader()`](/docs/headless/source-api), the routing functionality will be handled by your React framework. You can define folders and pages similar to file-system based routing. ## File For [MDX](https://mdxjs.com) & Markdown file, you can customise page information from frontmatter. ```mdx --- title: My Page description: Best document ever icon: HomeIcon --- ## Learn More ``` Fumadocs detects from the following properties to construct page trees. | name | description | | ------------- | ------------------------------------- | | `title` | The title of page | | `description` | The description of page | | `icon` | The name of icon, see [Icons](#icons) | Page information is supplied by the content source such as **Fumadocs MDX**. On Fumadocs MDX, you can specify a [`schema`](/docs/mdx/collections#schema-1) option to customise frontmatter schema. ### Slugs The slugs of a page are generated from its file path. | path (relative to content folder) | slugs | | --------------------------------- | ----------------- | | `./dir/page.mdx` | `['dir', 'page']` | | `./dir/index.mdx` | `['dir']` | ## Folder Organize multiple pages, you can create a [Meta file](#meta) to customise folders. ### Folder Group By default, putting a file into folder will change its slugs. You can wrap the folder name in parentheses to avoid impacting the slugs of child files. | path (relative to content folder) | slugs | | --------------------------------- | ---------- | | `./(group-name)/page.mdx` | `['page']` | ### Root Folder Marks the folder as a root folder, only items in the opened root folder will be visible. ```json title="meta.json" { "title": "Name of Folder", "description": "The description of root folder (optional)", "root": true } ``` For example, when you are opening root folder `framework`, the other folders (e.g. `headless`) are not shown on the sidebar and other navigation elements. Fumadocs UI renders root folders as [Sidebar Tabs](/docs/ui/navigation/sidebar#sidebar-tabs), which allows user to switch between them. ## Meta Customise folders by creating a `meta.json` file in the folder. ```json title="meta.json" { "title": "Display Name", "icon": "MyIcon", "pages": ["index", "getting-started"], "defaultOpen": true } ``` | name | description | | ------------- | ------------------------------------- | | `title` | Display name | | `icon` | The name of icon, see [Icons](#icons) | | `pages` | Folder items (see below) | | `defaultOpen` | Open the folder by default | ### Pages Folder items are sorted alphabetically by default, you can add or control the order of items using `pages`. ```json title="meta.json" { "pages": ["index", "getting-started"] } ``` > When specified, items are not included unless they are listed in `pages`. * **Path**: a path to page or folder, like`./nested/page`. * **Separator** (`---Label---`): a separator between two sections, use `---[Icon]Label---` to include icons. * **Link** (`[Text](url)`): insert links, use `[Icon][Text](url)` to include icons. Special Items: * **Rest** (`...`): include remaining pages (sorted alphabetically). * **Reversed Rest** (`z...a`): reversed **Rest** item. * **Extract** (`...folder`): extract the items from a folder. * **Except** (`!item`): Exclude an item from `...` or `z...a`. ```json title="meta.json" { "pages": [ "components", "---My Separator---", "...folder", "...", "!file", "!otherFolder", "[Vercel](https://vercel.com)", "[Triangle][Vercel](https://vercel.com)" ] } ``` ## Icons Since Fumadocs doesn't include an icon library, you have to convert the icon names to JSX elements in runtime, and render it as a component. You can add an [`icon` handler](/docs/headless/source-api#icons) to `loader()`. ## i18n Routing You can define different style for i18n routing. ```ts title="lib/i18n.ts" import type { I18nConfig } from 'fumadocs-core/i18n'; export const i18n: I18nConfig = { // default parser: 'dot', // or parser: 'dir', }; ``` Add Markdown/meta files for different languages by attending `.{locale}` to your file name, like: For the default locale, the locale code is optional. All content files are grouped by language folders: # Fumadocs Core (core library of framework): Page Tree URL: /docs/headless/page-tree Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/page-tree.mdx The structure of page tree. Page tree is a tree structure that describes all navigation links, with other items like separator and folders. It will be sent to the client and being referenced in navigation elements including the sidebar and breadcrumb. Hence, you shouldn't store any sensitive or large data in page tree. By design, page tree only contains necessary information of all pages and folders. Unserializable data such as functions can't be passed to page tree. ## Conventions The type definitions of page tree, for people who want to hardcode/generate it. You can also import the type from Fumadocs. ```ts import type * as PageTree from 'fumadocs-core/page-tree' const tree: PageTree.Root = { // props }; ``` Certain nodes contain a `$ref` property, they are internal and not used when hardcoding it. ### Root The initial root of page trees. ### Page ```json { "type": "page", "name": "Quick Start", "url": "/docs" } ``` > External urls are also supported ### Folder ```json { "type": "folder", "name": "Guide", "index": { "type": "page", ... } "children": [ ... ] } ``` ### Separator A label between items. ```json { "type": "separator", "name": "Components" } ``` ## Icons Icon is a `ReactElement`, supported by pages and folders. # Fumadocs MDX (the built-in content source): Async Mode URL: /docs/mdx/async Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/async.mdx Runtime compilation of content files. ## Introduction By default, all Markdown and MDX files need to be pre-compiled first. The same constraint also applies to the development server. This may result in longer dev server start times for large docs sites. You can enable Async Mode on docs collections to improve this. ## Setup Enable Async Mode. Docs Collection Doc Collection ```ts import { defineDocs } from 'fumadocs-mdx/config'; export const docs = defineDocs({ dir: 'content/docs', docs: { // [!code ++] async: true, }, }); ``` ```ts import { defineCollections } from 'fumadocs-mdx/config'; export const doc = defineCollections({ type: 'doc', dir: 'content/docs', // [!code ++] async: true, }); ``` ### Next.js Turbopack doesn't support lazy bundling at the moment, Async Mode works by performing on-demand compilation with MDX Remote. You need to install extra dependencies: npm pnpm yarn bun ```bash npm i @fumadocs/mdx-remote shiki ``` ```bash pnpm add @fumadocs/mdx-remote shiki ``` ```bash yarn add @fumadocs/mdx-remote shiki ``` ```bash bun add @fumadocs/mdx-remote shiki ``` Async Mode comes with some limitations on MDX features for Next.js. * **No import/export allowed in MDX files.** For MDX components, pass them from the `components` prop instead. * **Images must be referenced with URL (e.g. `/images/test.png`).** Don't use **file paths** like `./image.png`. You should locate your images in the `public` folder and reference them with URLs. ### Vite Vite has native support for lazy bundling, you only need to update `loader()` after enabling it. ```tsx title="lib/source.ts" import { loader } from 'fumadocs-core/source'; import { docs, create } from '@/.source'; export const source = loader({ baseUrl: '/docs', // [!code --] source: await create.sourceAsync(docs.doc, docs.meta), // [!code ++] source: await create.sourceLazy(docs.doc, docs.meta), }); ``` ## Usage Frontmatter properties are still available on `page` object, but you need to invoke the `load()` async function to load the compiled content (and its exports). For example: ```tsx title="page.tsx" import { source } from '@/lib/source'; import { getMDXComponents } from '@/mdx-components'; const page = source.getPage(['...']); if (page) { // frontmatter properties are available console.log(page.data); // Markdown content requires await const { body: MdxContent, toc } = await page.data.load(); console.log(toc); return ; } ``` When using Async Mode, we highly recommend to use third-party services to implement search, which usually have better capability to handle massive amount of content to index. # Fumadocs MDX (the built-in content source): Collections URL: /docs/mdx/collections Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/collections.mdx Collection of content data for your app ## Define Collections Define a collection to parse a certain set of files. ```ts import { defineCollections } from 'fumadocs-mdx/config'; import { z } from 'zod'; export const blog = defineCollections({ type: 'doc', dir: './content/blog', schema: z.object({ // schema }), // other options }); ``` ### `type` The accepted type of collection. ```ts import { defineCollections } from 'fumadocs-mdx/config'; // only scan for json/yaml files export const metaFiles = defineCollections({ type: 'meta', // options }); ``` * `type: meta` Accept JSON/YAML files, available options: | undefined", "simplifiedType": "function | Schema", "required": false, "deprecated": false }, { "name": "dir", "description": "Directories to scan", "tags": [], "type": "string | string[]", "simplifiedType": "array | string", "required": true, "deprecated": false }, { "name": "files", "description": "what files to include/exclude (glob patterns)\n\nInclude all files if not specified", "tags": [], "type": "string[] | undefined", "simplifiedType": "array", "required": false, "deprecated": false } ] }} /> * `type: doc` Markdown/MDX documents, available options: | undefined", "simplifiedType": "Partial", "required": false, "deprecated": false }, { "name": "mdxOptions", "description": "", "tags": [], "type": "ProcessorOptions | undefined", "simplifiedType": "ProcessorOptions", "required": false, "deprecated": false }, { "name": "async", "description": "Load files with async", "tags": [], "type": "Async | undefined", "simplifiedType": "Async", "required": false, "deprecated": false }, { "name": "schema", "description": "", "tags": [], "type": "CollectionSchema | undefined", "simplifiedType": "function | Schema", "required": false, "deprecated": false }, { "name": "dir", "description": "Directories to scan", "tags": [], "type": "string | string[]", "simplifiedType": "array | string", "required": true, "deprecated": false }, { "name": "files", "description": "what files to include/exclude (glob patterns)\n\nInclude all files if not specified", "tags": [], "type": "string[] | undefined", "simplifiedType": "array", "required": false, "deprecated": false } ] }} /> ### `schema` The schema to validate file data (frontmatter on `doc` type, content on `meta` type). ```ts import { defineCollections } from 'fumadocs-mdx/config'; import { z } from 'zod'; export const blog = defineCollections({ type: 'doc', dir: './content/blog', schema: z.object({ name: z.string(), }), }); ``` > [Standard Schema](https://standardschema.dev) compatible libraries, including Zod, are supported. Note that the validation is done at build time, hence the output must be serializable. You can also pass a function that receives the transform context. ```ts import { defineCollections } from 'fumadocs-mdx/config'; import { z } from 'zod'; export const blog = defineCollections({ type: 'doc', dir: './content/blog', schema: (ctx) => { return z.object({ name: z.string(), testPath: z.string().default( // original file path ctx.path, ), }); }, }); ``` ### `mdxOptions` Customise MDX options at the collection level. > This API is only available on `doc` type. ```ts title="source.config.ts" import { defineCollections, getDefaultMDXOptions } from 'fumadocs-mdx/config'; export const blog = defineCollections({ type: 'doc', mdxOptions: { // mdx options }, }); ``` By design, this will remove all default settings applied by your global config and Fumadocs MDX. You have full control over MDX options. You can use [`getDefaultMDXOptions`](/docs/mdx/mdx) to apply the default MDX preset. ```ts title="source.config.ts" import { defineCollections, getDefaultMDXOptions } from 'fumadocs-mdx/config'; export const blog = defineCollections({ type: 'doc', mdxOptions: getDefaultMDXOptions({ // extend the preset }), }); ``` ### `postprocess` > This API is only available on `doc` type. See [Postprocess](/docs/mdx/postprocess). ## Define Docs Define a collection for Fumadocs. ```ts import { defineDocs } from 'fumadocs-mdx/config'; export const docs = defineDocs({ dir: '/my/content/dir', docs: { // optional, options of `doc` collection }, meta: { // optional, options of `meta` collection }, }); ``` ### `dir` Instead of per collection, you should customise content directory from `defineDocs`: ```ts import { defineDocs } from 'fumadocs-mdx/config'; export const docs = defineDocs({ dir: 'content/guides', }); ``` ### `schema` You can extend the default Zod 4 schema of `docs` and `meta`. ```ts import { frontmatterSchema, metaSchema, defineDocs } from 'fumadocs-mdx/config'; import { z } from 'zod'; export const docs = defineDocs({ docs: { schema: frontmatterSchema.extend({ index: z.boolean().default(false), }), }, meta: { schema: metaSchema.extend({ // other props }), }, }); ``` # Fumadocs MDX (the built-in content source): Global Options URL: /docs/mdx/global Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/global.mdx Customise Fumadocs MDX ## Global Options Shared options of Fumadocs MDX. ```ts title="source.config.ts" import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ // global options }); ``` Promise) | undefined", "simplifiedType": "function | ProcessorOptions & object | object & Omit", "required": false, "deprecated": false }, { "name": "lastModifiedTime", "description": "Fetch last modified time with specified version control", "tags": [ { "name": "defaultValue", "text": "'none'" } ], "type": "\"git\" | \"none\" | undefined", "simplifiedType": "\"none\" | \"git\"", "required": false, "deprecated": false }, { "name": "experimentalBuildCache", "description": "specify a directory to access & store cache (disabled during development mode).\n\nThe cache will never be updated, delete the cache folder to clean.", "tags": [], "type": "string | undefined", "simplifiedType": "string", "required": false, "deprecated": false } ] }} /> ### MDX Options Customise the [default MDX preset](/docs/mdx/mdx). ```ts title="source.config.ts" import { defineConfig } from 'fumadocs-mdx/config'; import rehypeKatex from 'rehype-katex'; import remarkMath from 'remark-math'; export default defineConfig({ mdxOptions: { remarkPlugins: [remarkMath], // When order matters rehypePlugins: (v) => [rehypeKatex, ...v], }, }); ``` Or using the minimal preset: ```ts title="source.config.ts" import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { preset: 'minimal', // now it accepts only MDX processor options }, }); ``` # Fumadocs MDX (the built-in content source): Include URL: /docs/mdx/include Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/include.mdx Reuse content from other files. ## Usage ### Content To display content from another Markdown/MDX file, specify its path relative to the file itself in the `` tag. MDX Markdown ```mdx ./another.mdx ``` ```md ::include[./another.mdx] ``` Markdown (`.md`) files do not support JSX syntax. To use ``, you need to configure `remark-directive`. ```ts title="source.config.ts" import { defineConfig } from 'fumadocs-mdx/config'; import remarkDirective from 'remark-directive'; export default defineConfig({ mdxOptions: { // [!code ++] remarkPlugins: [remarkDirective], }, }); ``` You may also need `rehype-raw` for HTML comments & content. ### CodeBlock For other types of files, it will become a codeblock: MDX Markdown ```mdx ./script.ts page.md ``` ```md ::include[./script.ts] ::include[page.md]{lang=md meta='title="lib.md"'} ``` ### `cwd` Resolve relative paths from cwd instead of the current file: MDX Markdown ```mdx ./script.ts ``` ```md ::include[./script.ts]{cwd lang=tsx meta='title="lib.ts"'} ``` ## Extraction When referencing content files, you can only include a certain part of the document. ### Section Encapsulate by a `
` tag: a.mdx b.mdx ```mdx ## Hello World
This is included
This is not included. ```
```mdx Include the content from `a.mdx`: a.mdx#test ```
In Markdown: a.md b.md ```md ## Hello World :::section{#test} This is included ::: This is not included. ``` ```md Include the content from `a.mdx`: ::include[a.md#test] ``` ### Heading Include contents under a certain heading. a.mdx b.mdx ```mdx ## Included Section I'm here! ## Not Included Some random text. ``` ```mdx Content under the heading: a.mdx#included-section ``` In Markdown: a.md b.md ```md ## Included Section I'm here! ## Not Included Some random text. ``` ```md Content under the heading: ::include[a.mdx#included-section] ``` # Fumadocs MDX (the built-in content source): Getting Started URL: /docs/mdx Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/index.mdx Introducing Fumadocs MDX, the official content source of Fumadocs. ## Quick Start Get started with Fumadocs MDX: Using Fumadocs MDX with Next.js. Using Fumadocs MDX with React Router 7 or above. Using Fumadocs MDX with Tanstack Start/Router. ## 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 compiler + validator, you can use it to handle blog posts and other contents. ### Defining Collections **Collection** refers to a collection containing a certain type of files. You can define collections in the config file (`source.config.ts`). Fumadocs MDX transforms collections into type-safe data, accessible in your app. Available collections: Compile Markdown & MDX files into a React Server Component, with useful properties like **Table of Contents**. ```ts title="source.config.ts" import { defineCollections } from 'fumadocs-mdx/config'; export const test = defineCollections({ type: 'doc', dir: 'content/docs', }); ``` Transform YAML/JSON files into an array of data. ```ts title="source.config.ts" import { defineCollections } from 'fumadocs-mdx/config'; export const test = defineCollections({ type: 'meta', dir: 'content/docs', }); ``` Combination of `meta` and `doc` collections, which is needed for Fumadocs. ```ts title="source.config.ts" import { defineDocs } from 'fumadocs-mdx/config'; export const docs = defineDocs({ dir: 'content/docs', docs: { // options for `doc` collection }, meta: { // options for `meta` collection }, }); ``` For example, a `doc` collection will transform the `.md` and `.mdx` files: ### Accessing Collections Collections will be compiled into JavaScript files that your app can access, the output format varies according to the framework you use. [Get started](#quickstart) with your framework to learn more. ## 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 | ### Customise Frontmatter Use the [`schema`](/docs/mdx/collections#schema-1) option to pass a validation schema to validate frontmatter and define its output properties. ### Customise MDX Compiler Fumadocs MDX uses [MDX Compiler](https://mdxjs.com/packages/mdx) to compile MDX files into JavaScript files. The [default preset](/docs/mdx/mdx) includes a set of plugins and configurations out-of-the-box. You can customise it on [Global Config](/docs/mdx/global#mdx-options) or [Collection Config](/docs/mdx/collections#mdxoptions). # Fumadocs MDX (the built-in content source): Last Modified Time URL: /docs/mdx/last-modified Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/last-modified.mdx Output the last modified time of a document ## Usage This feature is not enabled by default, you can enable this from the config file. Note that it only supports Git as version control. Please ensure you have Git installed on your machine, and **the repository is not shallow cloned**, as it relies on your local Git history. ```ts title="source.config.ts" import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ lastModifiedTime: 'git', // [!code highlight] }); ``` ### Access the Property After doing this, a `lastModified` number will be exported for each document. You can convert it to a JavaScript Date object. ```ts import { source } from '@/lib/source'; const page = source.getPage(['...']); console.log(new Date(page.data.lastModified)); // or with async mode: const { lastModified } = await page.data.load(); console.log(new Date(lastModified)); ``` # Fumadocs MDX (the built-in content source): MDX Presets URL: /docs/mdx/mdx Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/mdx.mdx Customise the default configurations for MDX processor. Fumadocs MDX provides MDX presets to simplify configurations of features like **syntax highlighting**. ## Default Preset This preset is enabled by default. To override the defaults, it accepts an interface inherited from [`ProcessorOptions`](https://mdxjs.com/packages/mdx/#processoroptions): ### Remark Plugins These plugins are applied by default: * [Remark Image](/docs/headless/mdx/remark-image) - Handle images * [Remark Heading](/docs/headless/mdx/headings) - Extract table of contents * [Remark Structure](/docs/headless/mdx/structure) - Generate search indexes * Remark Exports - Exports the output generated by remark plugins above Add other remark plugins with: Global Config Collection Config ```ts import { defineConfig } from 'fumadocs-mdx/config'; import { myPlugin } from './remark-plugin'; export default defineConfig({ mdxOptions: { remarkPlugins: [myPlugin], // You can also pass a function to control the order of remark plugins. remarkPlugins: (v) => [myPlugin, ...v], }, }); ``` ```ts import { defineCollections, getDefaultMDXOptions } from 'fumadocs-mdx/config'; import { myPlugin } from './remark-plugin'; export const blog = defineCollections({ type: 'doc', mdxOptions: getDefaultMDXOptions({ remarkPlugins: [myPlugin], // You can also pass a function to control the order of remark plugins. remarkPlugins: (v) => [myPlugin, ...v], }), }); ``` ### Rehype Plugins These plugins are applied by default: * [Rehype Code](/docs/headless/mdx/rehype-code) - Syntax highlighting Same as remark plugins, you can pass an array or a function to add other rehype plugins. Global Config Collection Config ```ts import { defineConfig } from 'fumadocs-mdx/config'; import { myPlugin } from './rehype-plugin'; export default defineConfig({ mdxOptions: { rehypePlugins: (v) => [myPlugin, ...v], }, }); ``` ```ts import { defineCollections, getDefaultMDXOptions } from 'fumadocs-mdx/config'; import { myPlugin } from './rehype-plugin'; export const blog = defineCollections({ type: 'doc', mdxOptions: getDefaultMDXOptions({ rehypePlugins: (v) => [myPlugin, ...v], }), }); ``` ### Customise Built-in Plugins Customise the options of built-in plugins like: Global Config Collection Config ```ts import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { rehypeCodeOptions: { // options }, remarkImageOptions: { // options }, remarkHeadingOptions: { // options }, }, }); ``` ```ts import { defineCollections, getDefaultMDXOptions } from 'fumadocs-mdx/config'; export const blog = defineCollections({ type: 'doc', mdxOptions: getDefaultMDXOptions({ rehypeCodeOptions: { // options }, remarkImageOptions: { // options }, remarkHeadingOptions: { // options }, }), }); ``` ### Export Properties from `vfile.data` Some remark plugins store their output in `vfile.data` (compile-time memory) which cannot be accessed from your code. Fumadocs MDX applies a remark plugin that turns `vfile.data` properties into ESM exports, so you can access these properties when importing the MDX file. You can define additional properties to be exported. Global Config Collection Config ```ts import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { valueToExport: ['dataName'], }, }); ``` ```ts import { defineCollections, getDefaultMDXOptions } from 'fumadocs-mdx/config'; export const blog = defineCollections({ type: 'doc', mdxOptions: getDefaultMDXOptions({ valueToExport: ['dataName'], }), }); ``` # Fumadocs MDX (the built-in content source): Import MDX Files URL: /docs/mdx/page Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/page.mdx Importing MDX files directly ## Introduction MDX files will be compiled into JavaScript with exports like: * `default`: the main component. * `frontmatter`: frontmatter data. * other properties generated by remark/rehype plugins. Hence, they can also be used as a page, or components that you can import. ### Using as Component You can import them as a component: ```tsx title="app/my-page.tsx" import MyPage from '@/content/page.mdx'; import { getMDXComponents } from '@/mdx-components'; export default function Page() { return (
{/* pass MDX components and render it */}
); } ``` ### Using as Page On Next.js, you can use `page.mdx` instead of `page.tsx` for creating a new page under the app directory. app/my-page/page.mdx components/layouts/page.tsx ```mdx --- title: My Page --- export { default } from '@/components/layouts/page'; # Hello World This is my page. ``` ```tsx import type { ComponentProps } from 'react'; export default function Content(props: ComponentProps<'main'>) { return (
{props.children}
); } ```
#### MDX Components It doesn't have MDX components by default, you have to provide them: mdx-components.tsx source.config.ts ```tsx import defaultMdxComponents from 'fumadocs-ui/mdx'; import type { MDXComponents } from 'mdx/types'; export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultMdxComponents, // for Fumadocs UI ...components, }; } // export a `useMDXComponents()` that returns MDX components export const useMDXComponents = getMDXComponents; // [!code ++] ``` ```ts import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { // Path to import your `mdx-components.tsx` above. [!code ++] providerImportSource: '@/mdx-components', }, }); ``` #### Other Frameworks? Other frameworks like Tanstack Start require explicit declaration of loaders, it's recommended to use them as components in your pages instead. # Fumadocs MDX (the built-in content source): Performance URL: /docs/mdx/performance Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/performance.mdx The performance of Fumadocs MDX ## Overview Fumadocs MDX is a bundler plugin, in other words, it has a higher performance bottleneck. With bundlers like Webpack and Turbopack, it is enough for large docs sites with nearly 500+ MDX files, which is sufficient for almost all use cases. Since Fumadocs MDX works with your bundler, you can import any files including client components in your MDX files. This allows high flexibility and ensures everything is optimized by default. ### Image Optimization Fumadocs MDX resolves images into static imports with [Remark Image](/docs/headless/mdx/remark-image). Therefore, your images will be optimized automatically for your React framework (e.g. Next.js Image API). ```mdx ![Hello](./hello.png) or in public folder ![Hello](/hello.png) ``` Yields: ```mdx import HelloImage from './hello.png'; Hello ``` Banner ## Caveats Although Fumadocs MDX can handle nearly 500+ files, it could be slow and inefficient. A huge amount of MDX files can cause extremely high memory usage during build and development mode. This is because of: * Bundlers do a lot of work under the hood to bundle MDX and JavaScript files and optimize performance. * Bundlers are not supposed to compile hundreds of MDX files. ### Solutions The main solution is to make the compilation on-demand, such that content is only loaded when it's being requested. #### Remote Source Remote sources don't need to pre-compile MDX files, it can compile them on-demand with SSG which can **highly increase your build speed.** However, you cannot use import in MDX files anymore. See [Custom Source](/docs/headless/custom-source) for configuring remote sources. #### Async Mode See [Async Mode](/docs/mdx/async). # Fumadocs MDX (the built-in content source): Postprocess URL: /docs/mdx/postprocess Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/postprocess.mdx Include useful information after compilation of content files. ## Configurations Note that `postprocess` is only available for doc/docs collections. ### Processed Markdown Include the processed Markdown content into static information before being converted into HTML. docs doc ```ts import { defineDocs } from 'fumadocs-mdx/config'; export default defineDocs({ docs: { postprocess: { // [!code ++] includeProcessedMarkdown: true, }, }, }); ``` ```ts import { defineCollections } from 'fumadocs-mdx/config'; export default defineCollections({ type: 'doc', postprocess: { // [!code ++] includeProcessedMarkdown: true, }, }); ``` You can obtain the included content with `getText()` (e.g. in Fumadocs): ```ts import { source } from '@/lib/source'; const page = source.getPage('...'); console.log(await page.data.getText('processed')); ``` # Fumadocs MDX (the built-in content source): Generate Types URL: /docs/mdx/typegen Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/typegen.mdx Generate types without running dev/build server ### Overview You can run `fumadocs-mdx` to generate types & index file (e.g. the `.source` folder in Next.js). Optionally, you can run it in post install to ensure types are generated when initializing the project. ```json title="package.json" { "scripts": { "postinstall": "fumadocs-mdx" } } ``` # openapi: Scalar Example URL: /docs/openapi Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/openapi/index.mdx View the Scalar Galaxy example OpenAPI schema. # Fumadocs Framework: Comparisons URL: /docs/ui/comparisons Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/comparisons.mdx How is Fumadocs different from other existing frameworks? ## Nextra Fumadocs is highly inspired by Nextra. For example, the Routing Conventions. That is why `meta.json` also exists in Fumadocs. Nextra is more opinionated than Fumadocs, as a side effect, you have to configure things manually compared to simply editing a configuration file. Fumadocs works great if you want more control over everything, such as adding it to an existing codebase or implementing advanced routing. ### Feature Table | Feature | Fumadocs | Nextra | | ------------------- | ------------ | ------------------------- | | Static Generation | Yes | Yes | | Cached | Yes | Yes | | Light/Dark Mode | Yes | Yes | | Syntax Highlighting | Yes | Yes | | Table of Contents | Yes | Yes | | Full-text Search | Yes | Yes | | i18n | Yes | Yes | | Last Git Edit Time | Yes | Yes | | Page Icons | Yes | Yes, via `_meta.js` files | | RSC | Yes | Yes | | Remote Source | Yes | Yes | | SEO | Via Metadata | Yes | | Built-in Components | Yes | Yes | | RTL Layout | Yes | Yes | ### Additional Features Features supported via 3rd party libraries like [TypeDoc](https://typedoc.org) will not be listed here. | Feature | Fumadocs | Nextra | | -------------------------- | -------- | ------ | | OpenAPI Integration | Yes | No | | TypeScript Docs Generation | Yes | No | | TypeScript Twoslash | Yes | Yes | ## Mintlify Mintlify is a documentation service, as compared to Fumadocs, it offers a free tier but isn't completely free and open source. Fumadocs is not as powerful as Mintlify, for example, the OpenAPI integration of Mintlify. As the creator of Fumadocs, I wouldn't recommend switching to Fumadocs from Mintlify if you're satisfied with the current way you build docs. However, I believe Fumadocs is a suitable tool for all React.js developers who want to have elegant docs. ## Docusaurus Docusaurus is a powerful framework based on React.js. It offers many cool features with plugins and custom themes. ### Lower Complexity As Fumadocs is designed to integrate with React frameworks, you may need more knowledge of React.js to get started. In return, Fumadocs have better customizability. For a simple docs, Docusaurus might be a better choice if you don't need any framework-specific functionality. ### Plugins You can easily achieve many things with plugins, their ecosystem is indeed larger and maintained by many contributors. In comparison, the flexibility of Fumadocs allows you to implement them on your own, it may take longer to tune it to your satisfaction. # Fumadocs Framework: Deploying URL: /docs/ui/deploying Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/deploying.mdx Deploy your Fumadocs app ## 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. * [Next.js](https://nextjs.org/docs/app/getting-started/deploying) * [React Router](https://reactrouter.com/start/framework/deploying) * [Tanstack Start](https://tanstack.com/start/latest/docs/framework/react/guide/hosting) * [Waku](https://waku.gg/#deployment) If you are looking to host the app as a SPA app (fully static, hostable on CDN), see [Static Build](/docs/ui/static-export). There's some extra notes on specific combinations: ### Next.js + Cloudflare Use [https://opennext.js.org/cloudflare](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 to the `WORKDIR` in the Dockerfile. The following snippet is taken from the official [Next.js Dockerfile Example](https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile): ```zsh title="Dockerfile" # syntax=docker.io/docker/dockerfile:1 FROM node:18-alpine AS base # Install dependencies only when needed FROM base AS deps # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. RUN apk add --no-cache libc6-compat WORKDIR /app # Install dependencies based on the preferred package manager [!code highlight] COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* source.config.ts ./ RUN \ if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ elif [ -f package-lock.json ]; then npm ci; \ elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \ else echo "Lockfile not found." && exit 1; \ fi # Rebuild the source code only when needed FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . # Next.js collects completely anonymous telemetry data about general usage. # Learn more here: https://nextjs.org/telemetry # Uncomment the following line in case you want to disable telemetry during the build. # ENV NEXT_TELEMETRY_DISABLED=1 RUN \ if [ -f yarn.lock ]; then yarn run build; \ elif [ -f package-lock.json ]; then npm run build; \ elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \ else echo "Lockfile not found." && exit 1; \ fi # Production image, copy all the files and run next FROM base AS runner WORKDIR /app ENV NODE_ENV=production # Uncomment the following line in case you want to disable telemetry during runtime. # ENV NEXT_TELEMETRY_DISABLED=1 RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs COPY --from=builder /app/public ./public # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static USER nextjs EXPOSE 3000 ENV PORT=3000 # server.js is created by next build from the standalone output # https://nextjs.org/docs/pages/api-reference/config/next-config-js/output ENV HOSTNAME="0.0.0.0" CMD ["node", "server.js"] ``` This ensures Fumadocs MDX can access your configuration file during builds. # Fumadocs Framework: Quick Start URL: /docs/ui Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/index.mdx Getting Started with Fumadocs ## Introduction Fumadocs (Foo-ma docs) is a **documentation framework**, designed to be fast, flexible, and composes seamlessly into your React framework. Fumadocs has different parts: } title="Fumadocs Core"> Handles most of the logic, including document search, content source adapters, and Markdown extensions. } title="Fumadocs UI"> The default theme of Fumadocs offers a beautiful look for documentation sites and interactive components. } title="Content Source"> The source of your content, can be a CMS or local data layers like [Fumadocs MDX](/docs/mdx) (the official content source). } title="Fumadocs CLI"> A command line tool to install UI components and automate things, useful for customizing layouts. Read our in-depth [What is Fumadocs](/docs/ui/what-is-fumadocs) introduction. ### Terminology **Markdown/MDX:** Markdown is a markup language for creating formatted text. Fumadocs natively supports Markdown and MDX (superset of Markdown). **[Bun](https://bun.sh):** A JavaScript runtime, we use it for running scripts. Some basic knowledge of React.js would be useful for further customisations. ## Automatic Installation A minimum version of Node.js 20 required. npm pnpm yarn bun ```bash npm create fumadocs-app ``` ```bash pnpm create fumadocs-app ``` ```bash yarn create fumadocs-app ``` ```bash bun create fumadocs-app ``` It will ask you the built-in template to use: * **React.js framework**: Next.js, Waku, React Router, Tanstack Start. * **Content source**: Fumadocs MDX. A new fumadocs app should be initialized. Now you can start hacking! You can follow the [Manual Installation](/docs/ui/manual-installation) guide to get started. ### Enjoy! Create your first MDX file in the docs folder. ```mdx title="content/docs/index.mdx" --- title: Hello World --- ## Yo what's up ``` Run the app in development mode and see [http://localhost:3000/docs](http://localhost:3000/docs). npm pnpm yarn bun ```bash npm run dev ``` ```bash pnpm run dev ``` ```bash yarn dev ``` ```bash bun run dev ``` ## FAQ Some common questions you may encounter. Make sure to upgrade Fumadocs when you've encountered any problems or trying out new features: ```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/*`): Or rename from `/docs/*` to `/*` using a route group: Finally, update the base URL of pages in `source.ts`: ```ts title="lib/source.ts" import { loader } from 'fumadocs-core/source'; export const source = loader({ baseUrl: '/info', // to the new value [!code highlight] }); ``` We recommend to use [Sidebar Tabs](/docs/ui/navigation/sidebar#sidebar-tabs). ### For Vite There's some weird pre-bundling problems with Vite: [#3910](https://github.com/vitejs/vite/issues/3910). Make sure to exclude Fumadocs from pre-bundling and add it to `noExternal`: ```ts title="vite.config.ts" import { defineConfig } from 'vite'; // add other Fumadocs deps as needed const FumadocsDeps = ['fumadocs-core', 'fumadocs-ui']; export default defineConfig({ resolve: { noExternal: FumadocsDeps, }, optimizeDeps: { exclude: FumadocsDeps, }, }); ``` ### For Next.js Node.js 23.1 might have problems with Next.js, see [#1021](https://github.com/fuma-nama/fumadocs/issues/1021). Make sure to change your Node.js version. 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/ui/static-export) 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`: For `/docs`, you need to change the catch-all route to be non-optional: ## 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](https://github.com/fuma-nama/fumadocs/discussions)! ### Writing Content For authoring docs, make sure to read: Fumadocs has some additional features for authoring content. Learn how to customise navigation links and sidebar items. Learn how to organise content. ### Special Needs # Fumadocs Framework: Obsidian URL: /docs/ui/obsidian Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/obsidian.mdx Render your Obsidian vaults in Fumadocs. Might have bugs or breaking changes, use it at your own risk. ## Setup npm pnpm yarn bun ```bash npm i fumadocs-obsidian ``` ```bash pnpm add fumadocs-obsidian ``` ```bash yarn add fumadocs-obsidian ``` ```bash bun add fumadocs-obsidian ``` You can copy your vault folder to the project (e.g. root directory): Create a script to generate docs & assets: ```ts title="scripts/generate.ts" import { fromVault } from 'fumadocs-obsidian'; await fromVault({ dir: 'Obsidian Vault', out: { // you can specify the locations of `/public` & `/content/docs` folder }, }); ``` Run the script to generate docs: ```bash bun scripts/generate.ts ``` Finally, include necessary MDX components: ```tsx title="mdx-components.tsx" import defaultMdxComponents from 'fumadocs-ui/mdx'; import * as ObsidianComponents from 'fumadocs-obsidian/ui'; import type { MDXComponents } from 'mdx/types'; export function getMDXComponents(components?: MDXComponents) { return { ...defaultMdxComponents, ...ObsidianComponents, ...components, }; } ``` ### Additions Some syntax features need to be enabled separately: * [Mermaid](/docs/ui/markdown/mermaid). * [Math](/docs/ui/markdown/math). # Fumadocs Framework: Page Slugs & Page Tree URL: /docs/ui/page-conventions Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/page-conventions.mdx A shared convention for organizing your documents > This guide only applies for content sources that uses `loader()` API, such as **Fumadocs MDX**. ## Overview Fumadocs generates **page slugs** and **page tree** (sidebar items) from your content directory using [`loader()`](/docs/headless/source-api), the routing functionality will be handled by your React framework. You can define folders and pages similar to file-system based routing. ## File For [MDX](https://mdxjs.com) & Markdown file, you can customise page information from frontmatter. ```mdx --- title: My Page description: Best document ever icon: HomeIcon --- ## Learn More ``` Fumadocs detects from the following properties to construct page trees. | name | description | | ------------- | ------------------------------------- | | `title` | The title of page | | `description` | The description of page | | `icon` | The name of icon, see [Icons](#icons) | Page information is supplied by the content source such as **Fumadocs MDX**. On Fumadocs MDX, you can specify a [`schema`](/docs/mdx/collections#schema-1) option to customise frontmatter schema. ### Slugs The slugs of a page are generated from its file path. | path (relative to content folder) | slugs | | --------------------------------- | ----------------- | | `./dir/page.mdx` | `['dir', 'page']` | | `./dir/index.mdx` | `['dir']` | ## Folder Organize multiple pages, you can create a [Meta file](#meta) to customise folders. ### Folder Group By default, putting a file into folder will change its slugs. You can wrap the folder name in parentheses to avoid impacting the slugs of child files. | path (relative to content folder) | slugs | | --------------------------------- | ---------- | | `./(group-name)/page.mdx` | `['page']` | ### Root Folder Marks the folder as a root folder, only items in the opened root folder will be visible. ```json title="meta.json" { "title": "Name of Folder", "description": "The description of root folder (optional)", "root": true } ``` For example, when you are opening root folder `framework`, the other folders (e.g. `headless`) are not shown on the sidebar and other navigation elements. Fumadocs UI renders root folders as [Sidebar Tabs](/docs/ui/navigation/sidebar#sidebar-tabs), which allows user to switch between them. ## Meta Customise folders by creating a `meta.json` file in the folder. ```json title="meta.json" { "title": "Display Name", "icon": "MyIcon", "pages": ["index", "getting-started"], "defaultOpen": true } ``` | name | description | | ------------- | ------------------------------------- | | `title` | Display name | | `icon` | The name of icon, see [Icons](#icons) | | `pages` | Folder items (see below) | | `defaultOpen` | Open the folder by default | ### Pages Folder items are sorted alphabetically by default, you can add or control the order of items using `pages`. ```json title="meta.json" { "pages": ["index", "getting-started"] } ``` > When specified, items are not included unless they are listed in `pages`. * **Path**: a path to page or folder, like`./nested/page`. * **Separator** (`---Label---`): a separator between two sections, use `---[Icon]Label---` to include icons. * **Link** (`[Text](url)`): insert links, use `[Icon][Text](url)` to include icons. Special Items: * **Rest** (`...`): include remaining pages (sorted alphabetically). * **Reversed Rest** (`z...a`): reversed **Rest** item. * **Extract** (`...folder`): extract the items from a folder. * **Except** (`!item`): Exclude an item from `...` or `z...a`. ```json title="meta.json" { "pages": [ "components", "---My Separator---", "...folder", "...", "!file", "!otherFolder", "[Vercel](https://vercel.com)", "[Triangle][Vercel](https://vercel.com)" ] } ``` ## Icons Since Fumadocs doesn't include an icon library, you have to convert the icon names to JSX elements in runtime, and render it as a component. You can add an [`icon` handler](/docs/headless/source-api#icons) to `loader()`. ## i18n Routing You can define different style for i18n routing. ```ts title="lib/i18n.ts" import type { I18nConfig } from 'fumadocs-core/i18n'; export const i18n: I18nConfig = { // default parser: 'dot', // or parser: 'dir', }; ``` Add Markdown/meta files for different languages by attending `.{locale}` to your file name, like: For the default locale, the locale code is optional. All content files are grouped by language folders: # Fumadocs Framework: Python URL: /docs/ui/python Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/python.mdx Generate docs from Python Support for Python docgen is still experimental, please use it in caution. ## Setup npm pnpm yarn bun ```bash npm install fumadocs-python shiki ``` ```bash pnpm add fumadocs-python shiki ``` ```bash yarn add fumadocs-python shiki ``` ```bash bun add fumadocs-python shiki ``` ### Generate Docs Install the Python command first, we need it to collect docs from your Python package. ```bash pip install ./node_modules/fumadocs-python ``` Generate the docs as a JSON: ```bash fumapy-generate your-package-name # for example fumapy-generate httpx ``` Use the following script to convert JSON into MDX: ```js title="scripts/generate-docs.mjs" import { rimraf } from 'rimraf'; import * as Python from 'fumadocs-python'; import * as fs from 'node:fs/promises'; // output JSON file path const jsonPath = './httpx.json'; async function generate() { const out = 'content/docs/(api)'; // clean previous output await rimraf(out); const content = JSON.parse((await fs.readFile(jsonPath)).toString()); const converted = Python.convert(content, { baseUrl: '/docs', }); await Python.write(converted, { outDir: out, }); } void generate(); ``` While most docgens use Markdown or reStructuredText, Fumadocs uses **MDX**. Make sure your doc is valid in MDX syntax before running. ### MDX Components Add the components. ```tsx import defaultMdxComponents from 'fumadocs-ui/mdx'; import type { MDXComponents } from 'mdx/types'; import * as Python from 'fumadocs-python/components'; export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultMdxComponents, ...Python, ...components, }; } ``` Add styles: ```css title="Tailwind CSS" @import 'tailwindcss'; @import 'fumadocs-ui/css/neutral.css'; @import 'fumadocs-ui/css/preset.css'; /* [!code ++] */ @import 'fumadocs-python/preset.css'; ``` # Fumadocs Framework: Static Build URL: /docs/ui/static-export Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/static-export.mdx Output static website with Fumadocs. ## Setup By default, Fumadocs use a server-first approach which always requires a running server to serve. You can output a static build by configuring your React framework.
### Search #### Built-in Search 1. [Configure Search Server](/docs/headless/search/orama#static-export). 2. [Configure Search UI/Client](/docs/ui/search/orama#static). After the configurations, your app will statically store the search indexes, and search will be computed on browser instead. #### Cloud Solutions Since the search functionality is powered by remote servers, static export works without configuration.
### Deployment #### Next.js You can enable Next.js static export, it allows you to export the app as a static HTML site without a Node.js server. ```js title="next.config.mjs" /** * @type {import('next').NextConfig} */ const nextConfig = { output: 'export', // Optional: Change links `/me` -> `/me/` and emit `/me.html` -> `/me/index.html` // trailingSlash: true, // Optional: Prevent automatic `/me` -> `/me/`, instead preserve `href` // skipTrailingSlashRedirect: true, }; ``` See [Next.js docs](https://nextjs.org/docs/app/guides/static-exports) for limitations and details. #### React Router By configuring SPA mode and pre-rendering, you can serve the `build/client` directory as a static website. ```ts title="react-router.config.ts" import type { Config } from '@react-router/dev/config'; import { glob } from 'node:fs/promises'; import { createGetUrl, getSlugs } from 'fumadocs-core/source'; const getUrl = createGetUrl('/docs'); export default { // disable SSR [!code highlight] ssr: false, // [!code ++:9] async prerender({ getStaticPaths }) { const paths: string[] = [...getStaticPaths()]; for await (const entry of glob('**/*.mdx', { cwd: 'content/docs' })) { paths.push(getUrl(getSlugs(entry))); } return paths; }, } satisfies Config; ``` #### Tanstack Start Configure SPA mode and pre-rendering, refer to [SPA Mode](https://tanstack.com/start/latest/docs/framework/react/guide/spa-mode) for deploying SPA apps with Tanstack Start. ```ts title="vite.config.ts" import { tanstackStart } from '@tanstack/react-start/plugin/vite'; import { defineConfig } from 'vite'; export default defineConfig({ plugins: [ // ...other plugins tanstackStart({ spa: { enabled: true, // Tanstack Router will automatically crawl your pages [!code ++:4] prerender: { enabled: true, }, // if you have any hidden paths that's not visible on UI, you can add them explicitly. [!code highlight:5] pages: [ { path: '/docs/test', }, ], }, }), ], }); ``` #### Waku Waku can serve your site statically when all pages are configured with `static` render mode. See [Deployment](https://waku.gg/#deployment) for details.
# Fumadocs Framework: Typescript URL: /docs/ui/typescript Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/typescript.mdx Generate docs from Typescript definitions ## Usage npm pnpm yarn bun ```bash npm install fumadocs-typescript ``` ```bash pnpm add fumadocs-typescript ``` ```bash yarn add fumadocs-typescript ``` ```bash bun add fumadocs-typescript ``` ### UI Integration It comes with the `AutoTypeTable` component. Learn more about [Auto Type Table](/docs/ui/components/auto-type-table). ### MDX Integration You can use it as a remark plugin: Fumadocs MDX MDX Compiler ```ts title="source.config.ts" import { remarkAutoTypeTable, createGenerator } from 'fumadocs-typescript'; import { defineConfig } from 'fumadocs-mdx/config'; const generator = createGenerator(); export default defineConfig({ mdxOptions: { remarkPlugins: [[remarkAutoTypeTable, { generator }]], }, }); ``` ```ts import { remarkAutoTypeTable, createGenerator } from 'fumadocs-typescript'; import { compile } from '@mdx-js/mdx'; const generator = createGenerator(); await compile('...', { remarkPlugins: [[remarkAutoTypeTable, { generator }]], }); ``` It gives you a `auto-type-table` component. You can use it like [Auto Type Table](/docs/ui/components/auto-type-table), but with additional rules: * The value of attributes must be string. * `path` accepts a path relative to the MDX file itself. * You also need to add [`TypeTable`](/docs/ui/components/type-table) to MDX components. ```ts title="path/to/file.ts" export interface MyInterface { name: string; } ``` ```mdx title="page.mdx" ``` ## Annotations ### Hide Hide a field by adding `@internal` tsdoc tag. ```ts interface MyInterface { /** * @internal */ cache: number; } ``` ### Specify Type Name You can specify the name of a type with the `@remarks` tsdoc tag. ```ts interface MyInterface { /** * @remarks `timestamp` Returned by API. // [!code highlight] */ time: number; } ``` This will make the type of `time` property to be shown as `timestamp`. # Fumadocs Framework: Versioning URL: /docs/ui/versioning Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/versioning.mdx Implementing multi-version in your docs. ## Overview It's common for developer tool related docs to version their docs, such as different docs for v1 and v2 of the same tool. Fumadocs provide the primitives for you to implement versioning on your own way. ## Partial Versioning When versioning only applies to part of your docs, You can separate them by folders. For example: You may want to group them with tabs rather than folders [using Sidebar Tabs](/docs/ui/navigation/sidebar#sidebar-tabs). ## Full Versioning Sometimes you want to version the entire website, such as [https://v14.fumadocs.dev](https://v14.fumadocs.dev) (Fumadocs v14) and [https://fumadocs.dev](https://fumadocs.dev) (Latest Fumadocs). You can create a Git branch for a version of docs (call it `v2` for example), and deploy it as a separate app on another subdomain like `v2.my-site.com`. Optionally, you can link to the other versions from your docs. This design allows some advantages over partial versioning: * Easy maintenance: Old docs/branches won't be affected when you iterate or upgrade dependencies. * Better consistency: Not just the docs itself, your landing page (and other pages) will also be versioned. # Fumadocs Framework: What is Fumadocs URL: /docs/ui/what-is-fumadocs Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/what-is-fumadocs.mdx Introducing Fumadocs, a docs framework that you can break. Fumadocs was created because the creator Fuma wanted a more customisable experience for building docs, to be a docs framework that is not opinionated, **a "framework" that you can break**. ## Philosophy ### Less Abstraction While most frameworks are configured with a configuration file, they usually lack flexibility when you hope to tune its details. You can’t control how they render the page nor the internal logic. Fumadocs expects you to write code and cooperate with the rest of your software, it shows you how the app works and fully customisable, instead of a configuration file. ### Seamless Integration Fumadocs integrates tightly with your React framework, bringing useful utilities and a good-looking UI. For Next.js developers, you are still using features of App Router, like **Static Site Generation**. There is nothing new for Next.js developers, that everything is familiar to you. ### Composable UI The only thing Fumadocs UI (the default theme) offers is **User Interface**. The UI is opinionated for bringing better mobile responsiveness and user experience. Instead, we follow a much more flexible approach inspired by Shadcn UI — [Fumadocs CLI](/docs/cli), allowing you to "fork" a part of Fumadocs UI, and fully customise it. ### Server-first Approach Traditionally, static site generators are **static**. However, Fumadocs introduce a server-first approach powered by React Server Component. With perfect server-client boundary, content becomes dynamic and interactive. You can fetch data from server to display content, or integrate with CMS receiving realtime updates. It is always up-to-date and easy to maintain. ### Minimal Fumadocs is maintained by Fuma and many contributors, with care on the maintainability of codebase. While we don't aim to offer every functionality people wanted, we're more focused on making basic features perfect and well-maintained. You can also help Fumadocs to be more useful by contributing! ## When to use Fumadocs Fumadocs is designed with flexibility in mind, it is not limited to certain usages. * `fumadocs-core` is a headless UI library for building docs. * `fumadocs-mdx` is a useful library to handle MDX content. For most of the web applications, vanilla React.js is no longer enough. Nowadays, we also wish to have a blog, a showcase page, a FAQ page, etc. In these cases, Fumadocs can help you build the docs easier, with less boilerplate. You can read [Comparisons](/docs/ui/comparisons) if you're interested. ### Documentation Fumadocs focuses on **authoring experience**, it provides a beautiful theme and many docs automation tools. It helps you to iterate your codebase faster while never leaving your docs behind. You can take this site as an example of docs site built with Fumadocs. ### Blog sites Most React.js frameworks can already suffice the needs of a blog site. Fumadocs provides additional tooling, including syntax highlighting, document search, and a default theme (Fumadocs UI). It helps you to avoid reinventing the wheels. # Fumadocs Core (core library of framework): Breadcrumb URL: /docs/headless/components/breadcrumb Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/components/breadcrumb.mdx The navigation component at the top of the screen A hook for implementing Breadcrumb in your documentation. It returns breadcrumb items for a page based on the given page tree. > If present, the index page of a folder will be used as the item. ## Usage It exports a `useBreadcrumb` hook: ```ts twoslash declare const tree: any; import { usePathname } from 'next/navigation'; // ---cut--- import { useBreadcrumb } from 'fumadocs-core/breadcrumb'; // obtain `pathname` using the hook provided by your React framework. const pathname = usePathname(); const items = useBreadcrumb(pathname, tree); // ^? ``` ### Example A styled example for Next.js. ```tsx 'use client'; import { usePathname } from 'next/navigation'; import { useBreadcrumb } from 'fumadocs-core/breadcrumb'; import type { PageTree } from 'fumadocs-core/page-tree'; import { Fragment } from 'react'; import { ChevronRight } from 'lucide-react'; import Link from 'next/link'; export function Breadcrumb({ tree }: { tree: PageTree.Root }) { const pathname = usePathname(); const items = useBreadcrumb(pathname, tree); if (items.length === 0) return null; return (
{items.map((item, i) => ( {i !== 0 && ( )} {item.url ? ( {item.name} ) : ( {item.name} )} ))}
); } ``` You can use it by passing the page tree via `tree` prop in a server component. ### Breadcrumb Item # Fumadocs Core (core library of framework): Components URL: /docs/headless/components Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/components/index.mdx Blocks for your docs # Fumadocs Core (core library of framework): Link URL: /docs/headless/components/link Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/components/link.mdx A Link component that handles external links A component that wraps the Link component of your React framework (e.g. `next/link`) and automatically handles external links in the document.\~ When an external URL is detected, it uses `` instead of the Link Component. The `rel` property is automatically generated. ## Usage Usage is the same as using ``. ```mdx import Link from 'fumadocs-core/link'; Click Me ``` ### External You can force a URL to be external by passing an `external` prop. ### Dynamic hrefs You can enable dynamic hrefs by importing `dynamic-link`. ```mdx import { DynamicLink } from 'fumadocs-core/dynamic-link'; Click Me ``` # Fumadocs Core (core library of framework): TOC URL: /docs/headless/components/toc Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/components/toc.mdx Table of Contents A Table of Contents with active anchor observer and auto scroll. ## Usage ```tsx import * as Base from 'fumadocs-core/toc'; return ( ); ``` ### Anchor Provider Watches for the active anchor using the Intersection Observer API. ### Scroll Provider Scrolls the scroll container to the active anchor. ### TOC Item An anchor item for jumping to the target anchor. | Data Attribute | Values | Description | | -------------- | ------------- | -------------------- | | `data-active` | `true, false` | Is the anchor active | ## Example ```tsx import { AnchorProvider, ScrollProvider, TOCItem, type TOCItemType, } from 'fumadocs-core/toc'; import { type ReactNode, useRef } from 'react'; export function Page({ items, children, }: { items: TOCItemType[]; children: ReactNode; }) { const viewRef = useRef(null); return (
{items.map((item) => ( {item.title} ))}
{children}
); } ``` # Fumadocs Core (core library of framework): Content Collections URL: /docs/headless/content-collections Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/content-collections/index.mdx Use Content Collections for Fumadocs [Content Collections](https://www.content-collections.dev) is a library that transforms your content into type-safe data collections. ## Setup Follow their [official guide](https://content-collections.dev/docs) to setup Content Collections for your React framework. Make sure you have MDX configured: npm pnpm yarn bun ```bash npm i @content-collections/mdx ``` ```bash pnpm add @content-collections/mdx ``` ```bash yarn add @content-collections/mdx ``` ```bash bun add @content-collections/mdx ``` To integrate with Fumadocs, add the following to your `content-collections.ts`. ```ts title="content-collections.ts" import { defineCollection, defineConfig } from '@content-collections/core'; import { frontmatterSchema, metaSchema, transformMDX, } from '@fumadocs/content-collections/configuration'; const docs = defineCollection({ name: 'docs', directory: 'content/docs', include: '**/*.mdx', schema: frontmatterSchema, transform: transformMDX, }); const metas = defineCollection({ name: 'meta', directory: 'content/docs', include: '**/meta.json', parser: 'json', schema: metaSchema, }); export default defineConfig({ collections: [docs, metas], }); ``` And pass it to `loader()`. ```ts title="lib/source.ts" import { allDocs, allMetas } from 'content-collections'; import { loader } from 'fumadocs-core/source'; import { createMDXSource } from '@fumadocs/content-collections'; export const source = loader({ baseUrl: '/docs', source: createMDXSource(allDocs, allMetas), }); ``` Done! You can access the pages and generated page tree from Source API. ```ts 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 customise MDX options in the `transformMDX` function. ```ts 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 `` component. ```tsx import { MDXContent } from '@content-collections/mdx/react'; import { getMDXComponents } from '@/mdx-components'; return ; ``` You can also import them in MDX Files, but it is not recommended. Content Collections uses `mdx-bundler` to bundle MDX files. 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. # Fumadocs Core (core library of framework): Headings URL: /docs/headless/mdx/headings Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/mdx/headings.mdx Process headings from your document ## Remark Heading Applies ids to headings. ```ts title="MDX Compiler" import { compile } from '@mdx-js/mdx'; import { remarkHeading } from 'fumadocs-core/mdx-plugins'; await compile('...', { remarkPlugins: [remarkHeading], }); ``` > This plugin is included by default on Fumadocs MDX. ### Extract TOC By default, it extracts the headings (table of contents) of a document to `vfile.data.toc`. You can disable it with: ```ts import { remarkHeading } from 'fumadocs-core/mdx-plugins'; export default { remarkPlugins: [[remarkHeading, { generateToc: false }]], }; ``` ### Custom Ids You can customise the heading id with `[#slug]`. ```md # heading [#slug] ``` ### Output An array of `TOCItemType`. ## Rehype TOC Exports table of contents (an array of `TOCItemType`), it allows JSX nodes which is not possible with a Remark plugin. > It requires MDX.js. ### Usage ```ts import { rehypeToc } from 'fumadocs-core/mdx-plugins'; export default { rehypePlugins: [rehypeToc], }; ``` ### Output For a Markdown document: ```md ## Hello `code` ``` An export will be created: ```jsx export const toc = [ { title: ( <> Hello code ), depth: 2, url: '#hello-code', }, ]; ``` # Fumadocs Core (core library of framework): MDX Plugins URL: /docs/headless/mdx Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/mdx/index.mdx Useful remark & rehype plugins for your docs. # Fumadocs Core (core library of framework): Package Install URL: /docs/headless/mdx/install Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/mdx/install.mdx Generate code blocks for installing packages For Fumadocs MDX, it is now enabled by default. You can use the `remarkNpm` plugin from `fumadocs-core/mdx-plugins` instead. ## Usage npm pnpm yarn bun ```bash npm install fumadocs-docgen ``` ```bash pnpm add fumadocs-docgen ``` ```bash yarn add fumadocs-docgen ``` ```bash bun add fumadocs-docgen ``` Add the remark plugin. Fumadocs MDX MDX Compiler ```ts title="source.config.ts" import { remarkInstall } from 'fumadocs-docgen'; import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { remarkPlugins: [remarkInstall], }, }); ``` ```ts import { compile } from '@mdx-js/mdx'; import { remarkInstall } from 'fumadocs-docgen'; await compile('...', { remarkPlugins: [remarkInstall], }); ``` Define the required components. ```tsx title="mdx-components.tsx (Fumadocs UI)" import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; import defaultComponents from 'fumadocs-ui/mdx'; import type { MDXComponents } from 'mdx/types'; export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultComponents, Tab, Tabs, ...components, }; } ``` | Component | | | --------- | ---------------------------------- | | Tabs | Accepts an array of item (`items`) | | Tab | Accepts the name of item (`value`) | Create code blocks with `package-install` as language. ````mdx ```package-install my-package ``` ```package-install npm i my-package -D ``` ```` ### Output The following structure should be generated by the plugin. ```mdx ... ... ... ... ``` npm pnpm yarn bun ```bash npm install my-package ``` ```bash pnpm add my-package ``` ```bash yarn add my-package ``` ```bash bun add my-package ``` ## Options ### Persistent When using with Fumadocs UI, you can enable persistence with the `persist` option. Fumadocs MDX MDX Compiler ```ts title="source.config.ts" import { remarkInstall } from 'fumadocs-docgen'; import { defineConfig } from 'fumadocs-mdx/config'; const remarkInstallOptions = { persist: { id: 'some-id', }, }; export default defineConfig({ mdxOptions: { remarkPlugins: [[remarkInstall, remarkInstallOptions]], }, }); ``` ```ts import { compile } from '@mdx-js/mdx'; import { remarkInstall } from 'fumadocs-docgen'; const remarkInstallOptions = { persist: { id: 'some-id', }, }; await compile('...', { remarkPlugins: [[remarkInstall, remarkInstallOptions]], }); ``` This will instead generate: ```mdx ... ``` # Fumadocs Core (core library of framework): Rehype Code URL: /docs/headless/mdx/rehype-code Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/mdx/rehype-code.mdx Code syntax highlighter A wrapper of [`@shikijs/rehype`](https://shiki.style/packages/rehype), the syntax highlighter used by Fumadocs. It converts raw `
` codeblocks into highlighted codeblocks:


  
    
      Input
    

    
      Output
    
  

  
    ````md
    ```ts
    console.log('Hello World');
    ```
    ````
  

  
    ```html
    
      
        
          console...
        
      
    
```
## Usage Add the rehype plugin. MDX Compiler Fumadocs MDX ```ts import { compile } from '@mdx-js/mdx'; import { rehypeCode, type RehypeCodeOptions } from 'fumadocs-core/mdx-plugins'; const rehypeCodeOptions: RehypeCodeOptions = { themes: { light: 'github-light', dark: 'github-dark', }, }; await compile('...', { rehypePlugins: [ // using default settings rehypeCode, // or with custom options [rehypeCode, rehypeCodeOptions], ], }); ``` ```ts import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { rehypeCodeOptions: { // enabled by default on Fumadocs MDX // configure options here }, }, }); ``` ### Meta It parses the `title` meta string, and adds it to the `pre` element as an attribute. Input Output ````mdx ```js title="Title" console.log('Hello'); ``` ```` ```jsx
...
```
You may filter the meta string before processing it with the `filterMetaString` option. ### Inline Code You can enable it with `inline` option: ```ts import { type RehypeCodeOptions } from 'fumadocs-core/mdx-plugins'; const rehypeCodeOptions: RehypeCodeOptions = { // [!code ++] inline: 'tailing-curly-colon', }; ``` ```md title="Syntax" This is a highlighted inline code: `console.log("hello world"){:js}`. ``` This is a highlighted inline code: `console.log("hello world"){:js}`. ### Icon The plugin will automatically adds an `icon` attribute according to the language meta string. It is a HTML string, you can render it with React `dangerouslySetInnerHTML`. Input Output ````md ```ts console.log('This should shows the logo of TypeScript'); ``` ```` ```jsx
...
```
Disable or customise icons with the `icon` option. ### More Options The options are inherited from [Shiki](https://shiki.style), see their docs for other options. # Fumadocs Core (core library of framework): Remark Admonition URL: /docs/headless/mdx/remark-admonition Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/mdx/remark-admonition.mdx Use Admonition in Fumadocs In Docusaurus, there's an [Admonition syntax](https://docusaurus.io/docs/markdown-features/admonitions). For people migrating from Docusaurus, you can enable this remark plugin to support the Admonition syntax. ## Usage Fumadocs MDX MDX Compiler ```ts title="source.config.ts" import { remarkAdmonition } from 'fumadocs-core/mdx-plugins'; import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { remarkPlugins: [remarkAdmonition], }, }); ``` ```ts import { compile } from '@mdx-js/mdx'; import { remarkAdmonition } from 'fumadocs-core/mdx-plugins'; await compile('...', { remarkPlugins: [remarkAdmonition], }); ``` ### Input ```md :::warning Hello World ::: ``` ### Output ```mdx Hello World ``` ### When to use We highly recommend using the JSX syntax of MDX instead. It's more flexible, some editors support IntelliSense in MDX files. ```mdx Hello World ``` # Fumadocs Core (core library of framework): Remark Image URL: /docs/headless/mdx/remark-image Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/mdx/remark-image.mdx Adding size attributes to images. This plugin adds `width` and `height` attributes to your image elements, which is needed for Image Optimization on Next.js and some other frameworks. ## Usage Add it to your Remark plugins. ```ts title="MDX Compiler" import { compile } from '@mdx-js/mdx'; import { remarkImage } from 'fumadocs-core/mdx-plugins'; await compile('...', { remarkPlugins: [remarkImage], }); ``` > This plugin is included by default on Fumadocs MDX. Supported: * Local Images * External URLs * Next.js static imports ### How It Works For Next.js, it uses **static imports** to import local images, which supports the `placeholder` option of Next.js Image. Next.js can handle image imports with its built-in image loader. Otherwise, it uses the file system or an HTTP request to download the image and obtain its size. ### Options ### Example: With Imports ```mdx ![Hello](/hello.png) ![Test](https://example.com/image.png) ``` Yields: ```mdx import HelloImage from './public/hello.png'; Hello Test ``` Where `./public/hello.png` points to the image in public directory. ### Example: Without Imports For Next.js, you can disable static imports on local images. ```ts import { remarkImage } from 'fumadocs-core/mdx-plugins'; export default { remarkPlugins: [[remarkImage, { useImport: false }]], }; ``` ```mdx ![Hello](/hello.png) ![Test](https://example.com/image.png) ``` Yields: ```mdx Hello Test ``` ### Example: Relative Paths When `useImport` is enabled, you can reference local images using relative paths. ```mdx ![Hello](./hello.png) ``` Be careful that using it with `useImport` disabled **doesn't work**. Next.js will not add the image to public assets unless you have imported it in code. For images in public directory, you can just reference them without relative paths. ### Example: Public Directory Customise the path of public directory ```ts import { remarkImage } from 'fumadocs-core/mdx-plugins'; import path from 'node:path'; export default { remarkPlugins: [ remarkImage, { publicDir: path.join(process.cwd(), 'dir'), }, ], }; ``` You can pass a URL too. ```ts import { remarkImage } from 'fumadocs-core/mdx-plugins'; export default { remarkPlugins: [ remarkImage, { publicDir: 'https://my-cdn.com/images', }, ], }; ``` # Fumadocs Core (core library of framework): Remark Files URL: /docs/headless/mdx/remark-mdx-files Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/mdx/remark-mdx-files.mdx Generate files from codeblocks. ## Introduction This plugin takes a codeblock like: ````md ```files project ├── src │ ├── index.js │ └── utils │ └── helper.js ├── package.json ``` ```` and convert into: ```mdx ``` ## Setup Add the remark plugin: Fumadocs MDX MDX Compiler ```tsx title="source.config.ts" import { remarkMdxFiles } from 'fumadocs-core/mdx-plugins'; import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { remarkPlugins: [remarkMdxFiles], }, }); ``` ```ts import { compile } from '@mdx-js/mdx'; import { remarkMdxFiles } from 'fumadocs-core/mdx-plugins'; await compile('...', { remarkPlugins: [remarkMdxFiles], }); ``` And make sure you have defined ``, ``, `` MDX components. # Fumadocs Core (core library of framework): Remark TS to JS URL: /docs/headless/mdx/remark-ts2js Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/mdx/remark-ts2js.mdx A remark plugin to transform TypeScript codeblocks into two tabs of codeblock with its JavaScript variant. ## Usage Install dependencies: npm pnpm yarn bun ```bash npm i fumadocs-docgen oxc-transform ``` ```bash pnpm add fumadocs-docgen oxc-transform ``` ```bash yarn add fumadocs-docgen oxc-transform ``` ```bash bun add fumadocs-docgen oxc-transform ``` For Next.js, externalize `oxc-transform`: ```js title="next.config.mjs (Next.js)" import { createMDX } from 'fumadocs-mdx/next'; /** @type {import('next').NextConfig} */ const config = { reactStrictMode: true, serverExternalPackages: ['oxc-transform'], }; const withMDX = createMDX(); export default withMDX(config); ``` Add the remark plugin: Fumadocs MDX MDX Compiler ```ts title="source.config.ts" import { remarkTypeScriptToJavaScript } from 'fumadocs-docgen/remark-ts2js'; import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { remarkPlugins: [remarkTypeScriptToJavaScript], }, }); ``` ```ts import { remarkTypeScriptToJavaScript } from 'fumadocs-docgen/remark-ts2js'; import { compile } from '@mdx-js/mdx'; await compile('...', { remarkPlugins: [remarkTypeScriptToJavaScript], }); ``` Finally, make sure to define the required MDX components: `Tabs` and `Tab`. ```tsx title="mdx-components.tsx (Fumadocs UI)" import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; import defaultComponents from 'fumadocs-ui/mdx'; import type { MDXComponents } from 'mdx/types'; export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultComponents, Tab, Tabs, ...components, }; } ``` You can now enable it on TypeScript/TSX codeblocks, like: ````md ```tsx ts2js import { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return
{children}
; } ``` ```` TypeScript JavaScript ```tsx import { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return
{children}
; } ```
```jsx export default function Layout({ children }) { return
{children}
; } ```
# Fumadocs Core (core library of framework): Remark Structure URL: /docs/headless/mdx/structure Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/mdx/structure.mdx Extract information from your documents, useful for implementing document search ## Usage Add it as a remark plugin. ```ts title="MDX Compiler" import { compile } from '@mdx-js/mdx'; import { remarkStructure } from 'fumadocs-core/mdx-plugins'; const vfile = await compile('...', { remarkPlugins: [remarkStructure], }); ``` > This plugin is included by default on Fumadocs MDX. Extracted information could be found in `vfile.data.structuredData`, you may write your own plugin to convert it into a MDX export. ### Options ### Output A list of headings and contents. Paragraphs will be extracted to the `contents` array, each item contains a `heading` prop indicating the heading of paragraph. A heading can have multiple paragraphs. #### Heading | Prop | | | --------- | ------------------------------------ | | `id` | unique identifier or slug of heading | | `content` | Text content | #### Content | Prop | | | --------- | ------------------------------- | | `heading` | Heading of paragraph (nullable) | | `content` | Text content | ## As a Function Accepts MDX/markdown content and return structurized data. ```ts import { structure } from 'fumadocs-core/mdx-plugins'; structure(page.body.raw); ``` If you have custom remark plugins enabled, such as `remark-math`, you have to pass these plugins to the function. This avoids unreadable content on paragraphs. ```ts import { structure } from 'fumadocs-core/mdx-plugins'; import remarkMath from 'remark-math'; structure(page.body.raw, [remarkMath]); ``` ### Parameters | Parameter | | | --------------- | ---------------------- | | `content` | MDX/markdown content | | `remarkPlugins` | List of remark plugins | | `options` | Custom options | # Fumadocs Core (core library of framework): Algolia Search URL: /docs/headless/search/algolia Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/search/algolia.mdx Integrate Algolia Search with Fumadocs If you're using Algolia's free tier, you have to [display their logo on your search dialog](https://algolia.com/policies/free-services-terms). ## Introduction The Algolia Integration automatically configures Algolia Search for document search. It creates a record for **each paragraph** in your document, it is also recommended by Algolia. Each record contains searchable attributes: | Attribute | Description | | --------- | --------------------- | | `title` | Page Title | | `section` | Heading ID (nullable) | | `content` | Paragraph content | The `section` field only exists in paragraphs under a heading. Headings and paragraphs are indexed as an individual record, grouped by their page ID. Notice that it expects the `url` property of a page to be unique, you shouldn't have two pages with the same url. ## Setup Install dependencies: npm pnpm yarn bun ```bash npm install algoliasearch ``` ```bash pnpm add algoliasearch ``` ```bash yarn add algoliasearch ``` ```bash bun add algoliasearch ``` ### Sign up on Algolia Sign up and obtain the app id and API keys for your search. Store these credentials in environment variables. ### Sync Search Indexes Pre-render a static route `/static.json` to export search indexes into production build: ```ts title="lib/export-search-indexes.ts" import { source } from '@/lib/source'; import type { DocumentRecord } from 'fumadocs-core/search/algolia'; export async function exportSearchIndexes() { const results: DocumentRecord[] = []; for (const page of source.getPages()) { results.push({ _id: page.url, structured: page.data.structuredData, url: page.url, title: page.data.title, description: page.data.description, }); } return results; } ``` Next.js React Router Tanstack Start Waku ```ts title="app/static.json/route.ts" import { exportSearchIndexes } from '@/lib/export-search-indexes'; export const revalidate = false; export async function GET() { return Response.json(await exportSearchIndexes()); } ``` ```ts title="app/routes/static.ts" import { exportSearchIndexes } from '@/lib/export-search-indexes'; export async function loader() { return Response.json(await exportSearchIndexes()); } ``` ```ts title="app/routes.ts" import { route, type RouteConfig } from '@react-router/dev/routes'; export default [ // [!code ++] route('static.json', 'routes/static.ts'), ] satisfies RouteConfig; ``` ```ts title="src/routes/static[.]json.ts" import { createFileRoute } from '@tanstack/react-router'; import { exportSearchIndexes } from '@/lib/export-search-indexes'; export const Route = createFileRoute('/static.json')({ server: { handlers: { GET: async () => Response.json(await exportSearchIndexes()), }, }, }); ``` ```ts title="vite.config.ts" import { tanstackStart } from '@tanstack/react-start/plugin/vite'; import { defineConfig } from 'vite'; export default defineConfig({ plugins: [ // ... tanstackStart({ prerender: { enabled: true, }, // [!code ++] pre-render the static file pages: [{ path: '/static.json' }], }), ], }); ``` ```ts title="pages/api/static.json.ts" import { exportSearchIndexes } from '@/lib/export-search-indexes'; export async function GET() { return Response.json(await exportSearchIndexes()); } export const getConfig = () => ({ render: 'static', }); ``` Make a script to sync search indexes: ```ts title="scripts/sync-content.ts" twoslash import { algoliasearch } from 'algoliasearch'; import { sync, DocumentRecord } from 'fumadocs-core/search/algolia'; import * as fs from 'node:fs'; // the path of pre-rendered `static.json`, choose one according to your React framework const filePath = { next: '.next/server/app/static.json.body', 'tanstack-start': '.output/public/static.json', 'react-router': 'build/client/static.json', waku: 'dist/public/static.json', }['next']; const content = fs.readFileSync(filePath); const records = JSON.parse(content.toString()) as DocumentRecord[]; const client = algoliasearch('id', 'key'); // update the index settings and sync search indexes void sync(client, { indexName: 'document', documents: records, }); ``` Now run the script after build: ```json title="package.json" { "scripts": { "build": "... && bun ./scripts/sync-content.ts" } } ``` ### Workflow You may manually upload search indexes with the script, or integrate it with your CI/CD pipeline. ### Search UI You can consider different options for implementing the UI: * Using [Fumadocs UI search dialog](/docs/ui/search/algolia). * Build your own using the built-in search client hook: ```ts twoslash import { liteClient } from 'algoliasearch/lite'; import { useDocsSearch } from 'fumadocs-core/search/client'; const client = liteClient('id', 'key'); const { search, setSearch, query } = useDocsSearch({ type: 'algolia', indexName: 'document', client, }); ``` * Use their official clients directly. ## Options ### Tag Filter To configure tag filtering, add a `tag` value to indexes. ```ts title="lib/export-search-indexes.ts" import { source } from '@/lib/source'; import type { DocumentRecord } from 'fumadocs-core/search/algolia'; export async function exportSearchIndexes() { const results: DocumentRecord[] = []; for (const page of source.getPages()) { results.push({ _id: page.url, structured: page.data.structuredData, url: page.url, title: page.data.title, description: page.data.description, // [!code ++] tag: '', }); } return results; } ``` And update your search client: * **Fumadocs UI**: Enable [Tag Filter](/docs/ui/search/algolia#tag-filter) on Search UI. * **Search Client**: You can add the tag filter like: ```ts import { useDocsSearch } from 'fumadocs-core/search/client'; const { search, setSearch, query } = useDocsSearch({ tag: '', // ... }); ``` The `tag` field is an attribute for faceting. You can also use the filter `tag:value` on Algolia search clients. # Fumadocs Core (core library of framework): Search URL: /docs/headless/search Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/search/index.mdx Configure Search in Fumadocs # Fumadocs Core (core library of framework): Mixedbread URL: /docs/headless/search/mixedbread Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/search/mixedbread.mdx Integrate Mixedbread Search with Fumadocs ## Introduction The Mixedbread Integration uses vector search to provide semantic search capabilities for your documentation. It indexes your documentation content into a vector store, enabling users to search using natural language queries and find relevant content based on meaning rather than just keyword matching. ## Setup ### Get your API Key 1. Sign up at [Mixedbread](https://platform.mixedbread.com) 2. Navigate to [API Keys](https://platform.mixedbread.com/platform?next=api-keys) 3. Create a new API key and store it in your environment variables ### Create a Vector Store To sync your documentation, you'll need to create a vector store: 1. Go to the [Vector Stores](https://platform.mixedbread.com/platform?next=vector-stores) in your Mixedbread dashboard 2. Create a new vector store for your documentation 3. Copy the vector store ID ### Sync Documentation Use the [Mixedbread CLI](https://www.mixedbread.com/cli) to sync your documentation: Install the CLI: npm pnpm yarn bun ```bash npm install @mixedbread/cli -D ``` ```bash pnpm add @mixedbread/cli -D ``` ```bash yarn add @mixedbread/cli --dev ``` ```bash bun add @mixedbread/cli --dev ``` Configure authentication and sync your documentation: ```bash # Configure authentication mxbai config keys add YOUR_API_KEY # Sync your documentation mxbai vs sync YOUR_VECTOR_STORE_ID "./content/docs" ``` The CLI will automatically detect changes in your documentation and update the vector store accordingly. ### Workflow You can automatically sync your documentation by adding a sync script to your `package.json`: ```json { "scripts": { "build": "... && mxbai vs sync YOUR_VECTOR_STORE_ID './content/docs' --ci" } } ``` ## Options ### Tag Filter To filter search results by tags, add a tag field to your document metadata: ```md --- title: Mixedbread description: Integrate Mixedbread Search with Fumadocs url: /docs/headless/search/mixedbread // [!code ++] tag: docs --- ``` And update your search client: * **Fumadocs UI**: Enable [Tag Filter](/docs/ui/search/orama#tag-filter) on Search UI. * **Search Client**: You can add the tag filter like: ```ts import { useDocsSearch } from 'fumadocs-core/search/client'; const { search, setSearch, query } = useDocsSearch({ tag: '', // ... }); ``` This allows you to scope searches to specific sections of your documentation. # Fumadocs Core (core library of framework): Orama Cloud URL: /docs/headless/search/orama-cloud Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/search/orama-cloud.mdx Integrate with Orama Cloud To begin, create an account on Orama Cloud. ## REST API REST API integration requires your docs to upload the indexes. 1. Create a new project with **REST API** data source on Orama Cloud dashboard. Store your credentials in environment variables, for example: ```dotenv NEXT_PUBLIC_ORAMA_DATASOURCE_ID="Rest API data source ID" NEXT_PUBLIC_ORAMA_PROJECT_ID="project ID" NEXT_PUBLIC_ORAMA_API_KEY="public API key" ORAMA_PRIVATE_API_KEY="private API key" ``` 2. Export the search indexes by pre-rendering a static route. ```ts title="lib/export-search-indexes.ts" import { source } from '@/lib/source'; import type { OramaDocument } from 'fumadocs-core/search/orama-cloud'; export async function exportSearchIndexes() { return source.getPages().map((page) => { return { id: page.url, structured: page.data.structuredData, url: page.url, title: page.data.title, description: page.data.description, } satisfies OramaDocument; }); } ``` Next.js React Router Tanstack Start Waku ```ts title="app/static.json/route.ts" import { exportSearchIndexes } from '@/lib/export-search-indexes'; export const revalidate = false; export async function GET() { return Response.json(await exportSearchIndexes()); } ``` ```ts title="app/routes/static.ts" import { exportSearchIndexes } from '@/lib/export-search-indexes'; export async function loader() { return Response.json(await exportSearchIndexes()); } ``` ```ts title="app/routes.ts" import { route, type RouteConfig } from '@react-router/dev/routes'; export default [ // [!code ++] route('static.json', 'routes/static.ts'), ] satisfies RouteConfig; ``` ```ts title="src/routes/static[.]json.ts" import { createFileRoute } from '@tanstack/react-router'; import { exportSearchIndexes } from '@/lib/export-search-indexes'; export const Route = createFileRoute('/static.json')({ server: { handlers: { GET: async () => Response.json(await exportSearchIndexes()), }, }, }); ``` ```ts title="vite.config.ts" import { tanstackStart } from '@tanstack/react-start/plugin/vite'; import { defineConfig } from 'vite'; export default defineConfig({ plugins: [ // ... tanstackStart({ prerender: { enabled: true, }, // [!code ++] pre-render the static file pages: [{ path: '/static.json' }], }), ], }); ``` ```ts title="pages/api/static.json.ts" import { exportSearchIndexes } from '@/lib/export-search-indexes'; export async function GET() { return Response.json(await exportSearchIndexes()); } export const getConfig = () => ({ render: 'static', }); ``` 3. Create a script to sync search indexes. ```ts title="scripts/sync-content.ts" import { sync, type OramaDocument } from 'fumadocs-core/search/orama-cloud'; import * as fs from 'node:fs/promises'; import { OramaCloud } from '@orama/core'; // the path of pre-rendered `static.json`, choose one according to your React framework const filePath = { next: '.next/server/app/static.json.body', 'tanstack-start': '.output/public/static.json', 'react-router': 'build/client/static.json', waku: 'dist/public/static.json', }['next']; async function main() { const orama = new OramaCloud({ projectId: process.env.NEXT_PUBLIC_ORAMA_PROJECT_ID, apiKey: process.env.ORAMA_PRIVATE_API_KEY, }); const content = await fs.readFile(filePath); const records = JSON.parse(content.toString()) as OramaDocument[]; await sync(orama, { index: process.env.NEXT_PUBLIC_ORAMA_DATASOURCE_ID, documents: records, }); console.log(`search updated: ${records.length} records`); } void main(); ``` 4. Run the script after production build, make sure the environment variables are available (e.g. Bun reads from `.env` files): ```json title="package.json" { "scripts": { "build": "... && bun scripts/sync-content.ts" } } ``` ### Search Client To search documents on the client side, consider: * Using [Fumadocs UI search dialog](/docs/ui/search/orama-cloud). * Custom search UI using the built-in hook of Fumadocs: ```ts import { useDocsSearch } from 'fumadocs-core/search/client'; import { OramaCloud } from '@orama/core'; const client = new OramaCloud({ projectId: process.env.NEXT_PUBLIC_ORAMA_PROJECT_ID, apiKey: process.env.NEXT_PUBLIC_ORAMA_API_KEY, }); const { search, setSearch, query } = useDocsSearch({ type: 'orama-cloud', client, params: { // optional search params }, }); ``` * Use their search client directly. ## Web Crawler 1. Create a Crawler data source from dashboard, and configure it correctly with the "Documentation" preset. 2. Copy the credentials from dashboard. ### Search Client Same as REST API integration, but make sure to set `index` to `crawler`. ```ts import { useDocsSearch } from 'fumadocs-core/search/client'; import { OramaCloud } from '@orama/core'; const client = new OramaCloud({ projectId: '', apiKey: '', }); const { search, setSearch, query } = useDocsSearch({ type: 'orama-cloud', index: 'crawler', client, params: { // optional search params }, }); ``` It's same for Fumadocs UI. # Fumadocs Core (core library of framework): Built-in Search URL: /docs/headless/search/orama Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/search/orama.mdx Built-in document search of Fumadocs Fumadocs supports document search with Orama, It is the default but also the recommended option since it can be self-hosted and totally free. ## Setup Host the server for handling search requests. ### From Source Create the server from source object. Next.js React Router Tanstack Start Waku ```ts title="app/api/search/route.ts" import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; export const { GET } = createFromSource(source, { // https://docs.orama.com/docs/orama-js/supported-languages language: 'english', }); ``` ```ts title="app/docs/search.ts" import type { Route } from './+types/search'; import { createFromSource } from 'fumadocs-core/search/server'; import { source } from '@/lib/source'; const server = createFromSource(source, { // https://docs.orama.com/docs/orama-js/supported-languages language: 'english', }); export async function loader({ request }: Route.LoaderArgs) { return server.GET(request); } ``` ```ts title="src/routes/api/search.ts" import { createFileRoute } from '@tanstack/react-router'; import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; const server = createFromSource(source, { // https://docs.orama.com/docs/orama-js/supported-languages language: 'english', }); export const Route = createFileRoute('/api/search')({ server: { handlers: { GET: async ({ request }) => server.GET(request), }, }, }); ``` ```ts title="src/pages/api/search.ts" import { createFromSource } from 'fumadocs-core/search/server'; import { source } from '@/lib/source'; export const { GET } = createFromSource(source); ``` ### From Search Indexes Create the server from search indexes, each index needs a `structuredData` field. Usually, it is provided by your content source (e.g. Fumadocs MDX). You can also extract it from Markdown/MDX document using the [Remark Structure](/docs/headless/mdx/structure) plugin. Next.js React Router Tanstack Start Waku ```ts title="app/api/search/route.ts" import { source } from '@/lib/source'; import { createSearchAPI } from 'fumadocs-core/search/server'; export const { GET } = createSearchAPI('advanced', { language: 'english', indexes: source.getPages().map((page) => ({ title: page.data.title, description: page.data.description, url: page.url, id: page.url, structuredData: page.data.structuredData, })), }); ``` ```ts title="app/docs/search.ts" import type { Route } from './+types/search'; import { createSearchAPI } from 'fumadocs-core/search/server'; import { source } from '@/lib/source'; const server = createSearchAPI('advanced', { language: 'english', indexes: source.getPages().map((page) => ({ title: page.data.title, description: page.data.description, url: page.url, id: page.url, structuredData: page.data.structuredData, })), }); export async function loader({ request }: Route.LoaderArgs) { return server.GET(request); } ``` ```ts title="src/routes/api/search.ts" import { createFileRoute } from '@tanstack/react-router'; import { source } from '@/lib/source'; import { createSearchAPI } from 'fumadocs-core/search/server'; const server = createSearchAPI('advanced', { language: 'english', indexes: source.getPages().map((page) => ({ title: page.data.title, description: page.data.description, url: page.url, id: page.url, structuredData: page.data.structuredData, })), }); export const Route = createFileRoute('/api/search')({ server: { handlers: { GET: async ({ request }) => server.GET(request), }, }, }); ``` ```ts title="src/pages/api/search.ts" import { source } from '@/lib/source'; import { createSearchAPI } from 'fumadocs-core/search/server'; export const { GET } = createSearchAPI('advanced', { language: 'english', indexes: source.getPages().map((page) => ({ title: page.data.title, description: page.data.description, url: page.url, id: page.url, structuredData: page.data.structuredData, })), }); ``` ### Searching Documents You can search documents using: * **Fumadocs UI**: Supported out-of-the-box, see [Search UI](/docs/ui/search/orama) for details. * **Search Client**: ```ts twoslash import { useDocsSearch } from 'fumadocs-core/search/client'; const client = useDocsSearch({ type: 'fetch', }); ``` ## Configurations ### Tag Filter Support filtering results by tag, it's useful for implementing multi-docs similar to this documentation. ```ts import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; const server = createFromSource(source, { buildIndex(page) { return { title: page.data.title, description: page.data.description, url: page.url, id: page.url, structuredData: page.data.structuredData, // use your desired value, like page.slugs[0] [!code ++] tag: '', }; }, }); ``` and update your search client: * **Fumadocs UI**: Configure [Tag Filter](/docs/ui/search/orama#tag-filter) on Search UI. * **Search Client**: pass a tag to the hook. ```ts import { useDocsSearch } from 'fumadocs-core/search/client'; const client = useDocsSearch({ type: 'fetch', tag: '', // [!code ++] }); ``` ### Static Mode To support usage with static site, use `staticGET` from search server and make the route static or pre-rendered. Next.js React Router Tanstack Start Waku ```ts title="app/api/search/route.ts" import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; // statically cached [!code highlight:2] export const revalidate = false; export const { staticGET: GET } = createFromSource(source); ``` ```ts title="app/docs/search.ts" // make sure this route is pre-rendered in `react-router.config.ts`. export async function loader() { // [!code highlight] return server.staticGET(); } ``` ```ts title="src/routes/api/search.ts" import { createFileRoute } from '@tanstack/react-router'; export const Route = createFileRoute('/api/search')({ server: { handlers: { // [!code highlight] GET: async () => server.staticGET(), }, }, }); ``` ```ts title="vite.config.ts" import { tanstackStart } from '@tanstack/react-start/plugin/vite'; import { defineConfig } from 'vite'; export default defineConfig({ plugins: [ tanstackStart({ prerender: { enabled: true, }, // [!code ++] pre-render the index file pages: [{ path: '/api/search' }], }), ], }); ``` ```ts title="src/pages/api/search.ts" import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; // [!code highlight] export const { staticGET: GET } = createFromSource(source); // statically cached [!code highlight:3] export const getConfig = async () => ({ render: 'static', }); ``` > `staticGET` is also available on `createSearchAPI`. and update your search clients: * **Fumadocs UI**: use [static client](/docs/ui/search/orama#static) on Search UI. * **Search Client**: use `static` instead of `fetch`. ```ts import { useDocsSearch } from 'fumadocs-core/search/client'; const client = useDocsSearch({ type: 'static', }); ``` Static Search requires clients to download the exported search indexes. For large docs sites, it can be expensive. You should use cloud solutions like Orama Cloud or Algolia for these cases. ## Internationalization Make sure your language is on the Orama [Supported Languages](https://docs.orama.com/docs/orama-js/supported-languages) list. From Source From Search Indexes ```ts title="app/api/search/route.ts" import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; const server = createFromSource(source, { localeMap: { // [locale]: Orama options [!code ++:2] ru: { language: 'russian' }, en: { language: 'english' }, }, }); ``` ```ts import { source } from '@/lib/source'; import { createI18nSearchAPI } from 'fumadocs-core/search/server'; import { i18n } from '@/lib/i18n'; const server = createI18nSearchAPI('advanced', { i18n, // [!code ++] indexes: source.getLanguages().flatMap(({ language, pages }) => pages.map((page) => ({ title: page.data.title, description: page.data.description, structuredData: page.data.structuredData, id: page.url, url: page.url, locale: language === 'ru' ? 'russian' : 'english', // [!code ++] })), ), }); ``` For **Static Mode**, you should configure from client-side instead: ```tsx title="components/search.tsx" import { useDocsSearch } from 'fumadocs-core/search/client'; import { create } from '@orama/orama'; function initOrama(locale?: string) { return create({ schema: { _: 'string' }, // [!code ++] language: locale === 'ru' ? 'russian' : 'english', }); } function Search() { const client = useDocsSearch({ type: 'static', initOrama, }); // ... } ``` ### Special Languages Chinese and Japanese require additional configurations: npm pnpm yarn bun ```bash npm i @orama/tokenizers ``` ```bash pnpm add @orama/tokenizers ``` ```bash yarn add @orama/tokenizers ``` ```bash bun add @orama/tokenizers ``` createFromSource Static mode ```ts title="app/api/search/route.ts" import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; import { createTokenizer } from '@orama/tokenizers/mandarin'; export const { GET } = createFromSource(source, { localeMap: { // [locale]: Orama options cn: { components: { tokenizer: createTokenizer(), }, search: { threshold: 0, tolerance: 0, }, }, }, }); ``` ```tsx title="components/search.tsx" import { useDocsSearch } from 'fumadocs-core/search/client'; import { createTokenizer } from '@orama/tokenizers/mandarin'; import { create } from '@orama/orama'; // [!code focus:8] function initOrama(locale?: string) { return create({ schema: { _: 'string' }, components: { tokenizer: locale === 'cn' ? createTokenizer() : undefined, }, }); } function Search() { const client = useDocsSearch({ type: 'static', initOrama, }); // ... } ``` and update your search clients: * **Fumadocs UI**: No changes needed, Fumadocs UI handles this when you have i18n configured correctly. * **Search Client**: Add `locale` to the search client, this will only allow pages with specified locale to be searchable by the user. ```ts import { useDocsSearch } from 'fumadocs-core/search/client'; const { search, setSearch, query } = useDocsSearch({ type: 'fetch', locale: 'cn', }); ``` ## Headless You can host the search server on other backend such as Express and Elysia. ```ts import { initAdvancedSearch } from 'fumadocs-core/search/server'; const server = initAdvancedSearch({ // you still have to pass indexes }); server.search('query', { // you can specify `locale` and `tag` here }); ``` # Fumadocs Core (core library of framework): Trieve Search URL: /docs/headless/search/trieve Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/search/trieve.mdx Integrate Trieve Search with Fumadocs > This is a community maintained integration. ## Introduction The Trieve Integration automatically configures Trieve Search for site search. By default, it creates a chunk for **each paragraph** in your document, it is officially recommended by Trieve. ## Setup ### Install Dependencies npm pnpm yarn bun ```bash npm install trieve-ts-sdk trieve-fumadocs-adapter ``` ```bash pnpm add trieve-ts-sdk trieve-fumadocs-adapter ``` ```bash yarn add trieve-ts-sdk trieve-fumadocs-adapter ``` ```bash bun add trieve-ts-sdk trieve-fumadocs-adapter ``` ### Sign up on Trieve Sign up and create a dataset. Then obtain 2 API keys where one has only read access and the other has admin access to create and delete chunks. Store these credentials in environment variables. One API Key should have only read access for the public facing search and the other should have admin access to create and delete chunks. ### Sync Dataset Export the search indexes by pre-rendering a static route. ```ts title="lib/export-search-indexes.ts" import { source } from '@/lib/source'; import { type TrieveDocument } from 'trieve-fumadocs-adapter/search/sync'; export async function exportSearchIndexes() { const results: TrieveDocument[] = []; for (const page of source.getPages()) { results.push({ _id: page.url, structured: page.data.structuredData, url: page.url, title: page.data.title, description: page.data.description, }); } return results; } ``` Next.js React Router Tanstack Start Waku ```ts title="app/static.json/route.ts" import { exportSearchIndexes } from '@/lib/export-search-indexes'; export const revalidate = false; export async function GET() { return Response.json(await exportSearchIndexes()); } ``` ```ts title="app/routes/static.ts" import { exportSearchIndexes } from '@/lib/export-search-indexes'; export async function loader() { return Response.json(await exportSearchIndexes()); } ``` ```ts title="app/routes.ts" import { route, type RouteConfig } from '@react-router/dev/routes'; export default [ // [!code ++] route('static.json', 'routes/static.ts'), ] satisfies RouteConfig; ``` ```ts title="src/routes/static[.]json.ts" import { createFileRoute } from '@tanstack/react-router'; import { exportSearchIndexes } from '@/lib/export-search-indexes'; export const Route = createFileRoute('/static.json')({ server: { handlers: { GET: async () => Response.json(await exportSearchIndexes()), }, }, }); ``` ```ts title="vite.config.ts" import { tanstackStart } from '@tanstack/react-start/plugin/vite'; import { defineConfig } from 'vite'; export default defineConfig({ plugins: [ // ... tanstackStart({ prerender: { enabled: true, }, // [!code ++] pre-render the static file pages: [{ path: '/static.json' }], }), ], }); ``` ```ts title="pages/api/static.json.ts" import { exportSearchIndexes } from '@/lib/export-search-indexes'; export async function GET() { return Response.json(await exportSearchIndexes()); } export const getConfig = () => ({ render: 'static', }); ``` Create a script, the `sync` function will sync search indexes. ```ts title="scripts/sync-content.ts" import * as fs from 'node:fs'; import { sync, type TrieveDocument } from 'trieve-fumadocs-adapter/search/sync'; import { TrieveSDK } from 'trieve-ts-sdk'; // the path of pre-rendered `static.json`, choose one according to your React framework const filePath = { next: '.next/server/app/static.json.body', 'tanstack-start': '.output/public/static.json', 'react-router': 'build/client/static.json', waku: 'dist/public/static.json', }['next']; const content = fs.readFileSync(filePath); const records: TrieveDocument[] = JSON.parse(content.toString()); const client = new TrieveSDK({ apiKey: 'adminApiKey', datasetId: 'datasetId', }); sync(client, records); ``` Make sure to run the script after build: ```json title="package.json" { "scripts": { "build": "... && bun scripts/sync-content.ts" } } ``` > You can also integrate it with your CI/CD pipeline. ### Search UI You can use their `SearchDialog` component: ```tsx title="components/search.tsx" 'use client'; import type { SharedProps } from 'fumadocs-ui/components/dialog/search'; import SearchDialog from 'trieve-fumadocs-adapter/components/dialog/search'; import { TrieveSDK } from 'trieve-ts-sdk'; const trieveClient = new TrieveSDK({ apiKey: 'readOnlyApiKey', datasetId: 'datasetId', }); export default function CustomSearchDialog(props: SharedProps) { return ; } ``` 1. Replace `apiKey` and `datasetId` with your desired values. 2. Replace the default search dialog with your new one. ### Search Client Add the `useTrieveSearch` hook: ```ts import { TrieveSDK } from 'trieve-ts-sdk'; import { useTrieveSearch } from 'trieve-fumadocs-adapter/search/trieve'; const client = new TrieveSDK({ apiKey: 'readOnlyApiKey', datasetId: 'datasetId', }); const { search, setSearch, query } = useTrieveSearch(client); ``` ## Options ### Tag Filter To configure tag filtering, add a `tag` value to indexes. ```js import { sync } from 'trieve-fumadocs-adapter/search/sync'; import { TrieveSDK } from 'trieve-ts-sdk'; const client = new TrieveSDK({ apiKey: 'adminApiKey', datasetId: 'datasetId', }); const documents = records.map((index) => ({ ...index, tag: 'value', // [!code highlight] })); sync(client, documents); ``` #### Search UI Enable Tag Filter. ```tsx title="components/search.tsx" import SearchDialog from 'trieve-fumadocs-adapter/components/dialog/search'; ; ``` #### Search Client The `tag_set` field is an attribute for filtering. To filter indexes by tag, use the filter on Trieve search clients. ```json { "must": [ { "field": "tag_set", "match": ["value"] } ] } ``` Or with `useTrieveSearch` hook: ```ts import { TrieveSDK } from 'trieve-ts-sdk'; import { useTrieveSearch } from 'trieve-fumadocs-adapter/search/trieve'; const client = new TrieveSDK({ apiKey: 'readOnlyApiKey', datasetId: 'datasetId', }); const { search, setSearch, query } = useTrieveSearch( client, undefined, '', ); ``` # Fumadocs Core (core library of framework): Loader API URL: /docs/headless/source-api Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/source-api/index.mdx Turn a content source into a unified interface ## What it does? `loader()` provides an interface for Fumadocs to integrate with file-system based content sources. * Generate [page slugs and page tree](/docs/headless/page-conventions). * Assign URL to each page. * Output useful utilities to interact with content. It doesn't rely on the real file system (zero `node:fs` usage), a virtual storage is also allowed. ## Usage You can use it with built-in content sources like Fumadocs MDX. ```ts import { loader } from 'fumadocs-core/source'; import { docs } from '@/.source'; 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. ```ts import { loader } from 'fumadocs-core/source'; loader({ baseUrl: '/docs', // or you can customise it with function url(slugs, locale) { if (locale) return '/' + [locale, 'docs', ...slugs].join('/'); return '/' + ['docs', ...slugs].join('/'); }, }); ``` ### Icons Load the [icon](/docs/headless/page-conventions#icons) property specified by pages and meta files. ```ts 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. ```ts title="lib/source.ts" 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`](/docs/headless/internationalization#fallback-language). ## Output The loader outputs a source object. ### Get Page Get page with slugs. ```ts 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. ```ts import { source } from '@/lib/source'; // from any locale source.getPages(); // for a specific locale source.getPages('locale'); ``` ### Page Tree ```ts import { source } from '@/lib/source'; // without i18n source.pageTree; // with i18n source.pageTree['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. ```ts 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). ```ts title="app/[[...slug]]/page.tsx" import { source } from '@/lib/source'; export function generateStaticParams() { return source.generateParams(); } ``` ### Language Entries Get available languages and its pages. ```ts import { source } from '@/lib/source'; // language -> pages const entries = source.getLanguages(); ``` # Fumadocs Core (core library of framework): Loader Plugins URL: /docs/headless/source-api/plugins Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/source-api/plugins.mdx Extend Loader API ## Overview Loader plugins can extend `loader()` to customise its output. A list of built-in plugins: * **Lucide Icons**: use icons from Lucide React (require `lucide-react` to be installed). ```ts import { loader } from 'fumadocs-core/source'; import { lucideIconsPlugin } from 'fumadocs-core/source/lucide-icons'; export const source = loader({ // ... plugins: [lucideIconsPlugin()], }); ``` ## Creating Plugins Each plugin is an object: ```ts import { loader } from 'fumadocs-core/source'; export const source = loader({ plugins: [ { transformStorage({ storage }) {}, transformPageTree: { // ... }, }, ], }); ``` ### Storage During the process, your input source files will be parsed and form a virtual storage in memory. To perform virtual file-system operations before processing, you can hook `transformStorage`. ```ts import { loader } from 'fumadocs-core/source'; export const source = loader({ plugins: [ { transformStorage({ storage }) { storage.read('my/path'); }, transformPageTree: { // ... }, }, ], }); ``` ### Page Tree The page tree is generated from your file system, some unnecessary information (e.g. unused frontmatter properties) will be filtered. You can customise it using the `transformPageTree`, such as attaching custom properties to nodes, or customising the display name of pages. ```tsx import { loader } from 'fumadocs-core/source'; export const source = loader({ plugins: [ { transformPageTree: { file(node, file) { // access the original (unfiltered) file data if (file) console.log(this.storage.read(file)); // modify nodes node.name = <>Some JSX Nodes here; return node; }, }, }, ], }); ``` # Fumadocs Core (core library of framework): Source URL: /docs/headless/source-api/source Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/source-api/source.mdx Setup sources for Loader API ## Overview `loader()` accepts different content sources based on a `source` interface. ### Multiple Sources Use `multiple` to combine multiple sources into one. ```ts import { loader, multiple } from 'fumadocs-core/source'; import { openapiPlugin, openapiSource } from 'fumadocs-openapi/server'; import { blog, docs } from '@/.source'; import { lucideIconsPlugin } from 'fumadocs-core/source/lucide-icons'; export const source = loader( multiple({ docs: docs.toFumadocsSource(), openapi: blog.toFumadocsSource(), }), { baseUrl: '/docs', }, ); ``` To access properties exclusive to each source: ```ts const page = source.getPage(['...']); if (page.data.type === 'docs') { console.log(page.data); } else { console.log(page.data); } ``` ### Custom Source To plug your own content source, create a `Source` object. Since Loader API doesn't rely on file system, file paths only allow virtual paths like `file.mdx` and `content/file.mdx`, `./file.mdx` and `D://content/file.mdx` are not allowed. ```ts twoslash import { Source } from 'fumadocs-core/source'; export function createMySource(): Source<{ metaData: { title: string; pages: string[] }; // Your custom type pageData: { title: string; description?: string }; // Your custom type }> { return { files: [ { type: 'page', path: 'folder/index.mdx', data: { title: 'Hello World', // ... }, }, { type: 'meta', path: 'meta.json', data: { title: 'Docs', pages: ['folder'], // ... }, }, ], }; } ``` # Fumadocs Core (core library of framework): Find Neighbours URL: /docs/headless/utils/find-neighbour Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/utils/find-neighbour.mdx Find the neighbours of a page from the page tree Find the neighbours of a page from the page tree, it returns the next and previous page of a given page. It is useful for implementing a footer. ## Usage It requires a page tree and the url of page. ```ts import { findNeighbour } from 'fumadocs-core/page-tree'; import { pageTree } from '@/lib/source'; const neighbours = findNeighbour(pageTree, '/url/to/page'); ``` | Parameter | Type | Description | | --------- | ---------- | --------------- | | tree | `PageTree` | The page tree | | url | `string` | The url of page | # Fumadocs Core (core library of framework): Get TOC URL: /docs/headless/utils/get-toc Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/utils/get-toc.mdx Parse Table of contents from markdown/mdx content Parse Table of contents from markdown/mdx content. > [You can use the remark plugin directly](/docs/headless/mdx/headings) ## Usage Note: If you're using a CMS, you should use the API provided by the CMS instead. ```ts import { getTableOfContents } from 'fumadocs-core/content/toc'; const toc = getTableOfContents('## markdown content'); ``` ### Output An array of [`TOCItemType`](/docs/headless/mdx/headings#output) is returned. # Fumadocs Core (core library of framework): Last Modified Time URL: /docs/headless/utils/git-last-edit Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/utils/git-last-edit.mdx Get the last edit time of a file in Github repository ## Usage Pass your repository name, and the path to file. ```ts import { getGithubLastEdit } from 'fumadocs-core/content/github'; const time = await getGithubLastEdit({ owner: 'fuma-nama', repo: 'fumadocs', // example: "content/docs/index.mdx" path: `content/docs/${page.path}`, }); ``` ### Github Token Notice that you may easily reach the rate limit in development mode. Hence, you should pass a Github token for a higher rate limit. Learn more about [Authenticating to the REST API](https://docs.github.com/en/rest/overview/authenticating-to-the-rest-api). ```ts import { getGithubLastEdit } from 'fumadocs-core/content/github' const time = await getGithubLastEdit({ ..., token: `Bearer ${process.env.GIT_TOKEN}` }) ``` Also, you can skip this in development mode if you don't need that functionality. ```ts process.env.NODE_ENV === 'development'? null : getGithubLastEdit(...) ``` # Fumadocs Core (core library of framework): Utilities URL: /docs/headless/utils Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/utils/index.mdx Utilities to provide extra functionality to your docs # Fumadocs Core (core library of framework): Negotiation URL: /docs/headless/utils/negotiation Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/utils/negotiation.mdx Tiny wrapper for negotiation features. ## Overview For React.js frameworks supporting middlewares, you can use the Negotiation API of Fumadocs for basic functionalities. It is a tiny wrapper of `negotiator`. ### Accept Markdown Serve markdown or HTML depending on the `Accept` header, this can improve the experience for AI agents. ```ts title="proxy.ts (Next.js)" import { NextRequest, NextResponse } from 'next/server'; import { isMarkdownPreferred, rewritePath } from 'fumadocs-core/negotiation'; const { rewrite: rewriteLLM } = rewritePath('/docs/*path', '/llms.mdx/*path'); export default function proxy(request: NextRequest) { if (isMarkdownPreferred(request)) { const result = rewriteLLM(request.nextUrl.pathname); if (result) { return NextResponse.rewrite(new URL(result, request.nextUrl)); } } return NextResponse.next(); } ``` # Fumadocs MDX (the built-in content source): Next.js URL: /docs/mdx/next Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/(integrations)/next.mdx Use Fumadocs MDX with Next.js ## Setup Set up Fumadocs MDX for your Next.js application.
### Installation npm pnpm yarn bun ```bash npm i fumadocs-mdx @types/mdx ``` ```bash pnpm add fumadocs-mdx @types/mdx ``` ```bash yarn add fumadocs-mdx @types/mdx ``` ```bash bun add fumadocs-mdx @types/mdx ``` Create the configuration file: ```ts title="source.config.ts" import { defineDocs, defineConfig } from 'fumadocs-mdx/config'; export const docs = defineDocs({ dir: 'content/docs', }); export default defineConfig(); ``` Add the plugin to Next.js config: ```js title="next.config.mjs" import { createMDX } from 'fumadocs-mdx/next'; /** @type {import('next').NextConfig} */ const config = { reactStrictMode: true, }; // [!code ++:4] const withMDX = createMDX({ // customise the config file path // configPath: "source.config.ts" }); // [!code highlight] export default withMDX(config); ``` The Next.js config must be a `.mjs` file since Fumadocs is ESM-only. Setup an import alias (optional): ```json title="tsconfig.json" { "compilerOptions": { "paths": { // [!code ++] "@/.source": [".source"] } } } ``` ### Integrate with Fumadocs You can create a `lib/source.ts` file and obtain Fumadocs source from the `docs` collection output. ```ts title="lib/source.ts" import { docs } from '@/.source'; import { loader } from 'fumadocs-core/source'; export const source = loader({ baseUrl: '/docs', source: docs.toFumadocsSource(), }); ``` The `.source` folder will be generated when you run `next dev` or `next build`. ### Done You can now write content in `content/docs` folder.
## Examples ### Accessing Content Generally, you'll interact with the collections through [`loader()`](/docs/headless/source-api#output), same for multiple `docs` collections. ```tsx import { source } from '@/lib/source'; const page = source.getPage(['slugs']); if (page) { // access page data [!code highlight] console.log(page.data); // frontmatter properties are also inside [!code highlight] console.log(page.data.title); } ``` To render the page, use `page.data.body` as a component. ```tsx import { getMDXComponents } from '@/mdx-components'; const MDX = page.data.body; // set your MDX components with `components` prop return ; ``` Without using `loader()`, you can also import the `.source` folder using its collection name: source.config.ts Usage ```ts import { defineDocs } from 'fumadocs-mdx/config'; export const docs = defineDocs({ dir: 'content/docs', docs: { // options for `doc` collection }, meta: { // options for `meta` collection }, }); ``` ```ts import { docs } from '@/.source'; console.log(docs); ``` # Fumadocs Framework: Export PDF URL: /docs/ui/export-pdf Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(integrations)/export-pdf.mdx Export docs pages as PDF documents ## Overview In general, we strongly recommend you to download the entire website (HTML files, etc) for offline browsing. In case if you want to export the page as a PDF, you can follow this guide. ## Setup Use Puppeteer to export PDF files. ```ts title="scripts/export-pdf.ts" import puppeteer from 'puppeteer'; import fs from 'node:fs/promises'; import path from 'node:path'; const browser = await puppeteer.launch(); const outDir = 'pdfs'; // update this [!code highlight] const urls = ['/docs/ui', '/docs/ui/customisations']; async function exportPdf(pathname: string) { const page = await browser.newPage(); await page.goto('http://localhost:3000' + pathname, { waitUntil: 'networkidle2', }); await page.pdf({ path: path.join(outDir, pathname.slice(1).replaceAll('/', '-') + '.pdf'), width: 950, printBackground: true, }); console.log(`PDF generated successfully for ${pathname}`); await page.close(); } await fs.mkdir(outDir, { recursive: true }); await Promise.all(urls.map(exportPdf)); await browser.close(); ``` Add the following to your Tailwind CSS file to disable navigation elements when printing: ```css @media print { #nd-docs-layout { --fd-sidebar-width: 0px !important; } #nd-sidebar { display: none; } } ``` You can now run the script: ```bash bun ./scripts/export-pdf.ts ``` ### Invisible Contents For invisible contents in accordions/tabs, you may need to temporarily override the MDX components. For example: ```tsx title="mdx-components.tsx" import defaultMdxComponents from 'fumadocs-ui/mdx'; import type { MDXComponents } from 'mdx/types'; import { Accordion, Accordions } from 'fumadocs-ui/components/accordion'; // you may use environment variable here [!code highlight] const isPrinting = true; export function getMDXComponents(components?: MDXComponents) { const PrintingAccordion: typeof Accordion = (props) => (

{props.title}

{props.children}
); return { ...defaultMdxComponents, // updated accordions: Accordion: isPrinting ? PrintingAccordion : Accordion, Accordions: isPrinting ? 'div' : Accordions, ...components, } satisfies MDXComponents; } ``` # Fumadocs Framework: Feedback URL: /docs/ui/feedback Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(integrations)/feedback.mdx Receive feedback from your users ## Overview Feedback is crucial for knowing what your reader thinks, and help you to further improve documentation content. ## Installation Install it using **Fumadocs CLI**. npm pnpm yarn bun ```bash npx @fumadocs/cli@latest add feedback ``` ```bash pnpm dlx @fumadocs/cli@latest add feedback ``` ```bash yarn dlx @fumadocs/cli@latest add feedback ``` ```bash bun x @fumadocs/cli@latest add feedback ``` ## Usage Now add the `` component to your docs page: ```tsx import { DocsPage } from 'fumadocs-ui/page'; import { Feedback } from '@/components/feedback'; import posthog from 'posthog-js'; export default async function Page() { return ( {/* at the bottom of page */} { 'use server'; await posthog.capture('on_rate_docs', feedback); }} /> ); } ``` * `onRateAction`: fired when user submit feedback. You can specify a server action, or any function (in client component). Such as reporting user feedback as a `on_rate_docs` event on PostHog. ### Integrating with GitHub Discussion To report your feedback to GitHub Discussion, make a custom `onRateAction`. You can copy this server action as a starting point: ```ts lib/github.ts import { App, Octokit } from 'octokit'; import type { ActionResponse, Feedback } from '@/components/feedback'; export const repo = 'fumadocs'; export const owner = 'fuma-nama'; export const DocsCategory = 'Docs Feedback'; let instance: Octokit | undefined; async function getOctokit(): Promise { if (instance) return instance; const appId = process.env.GITHUB_APP_ID; const privateKey = process.env.GITHUB_APP_PRIVATE_KEY; if (!appId || !privateKey) { throw new Error( 'No GitHub keys provided for Github app, docs feedback feature will not work.', ); } const app = new App({ appId, privateKey, }); const { data } = await app.octokit.request( 'GET /repos/{owner}/{repo}/installation', { owner, repo, headers: { 'X-GitHub-Api-Version': '2022-11-28', }, }, ); instance = await app.getInstallationOctokit(data.id); return instance; } interface RepositoryInfo { id: string; discussionCategories: { nodes: { id: string; name: string; }[]; }; } let cachedDestination: RepositoryInfo | undefined; async function getFeedbackDestination() { if (cachedDestination) return cachedDestination; const octokit = await getOctokit(); const { repository, }: { repository: RepositoryInfo; } = await octokit.graphql(` query { repository(owner: "${owner}", name: "${repo}") { id discussionCategories(first: 25) { nodes { id name } } } } `); return (cachedDestination = repository); } export async function onRateAction( url: string, feedback: Feedback, ): Promise { 'use server'; const octokit = await getOctokit(); const destination = await getFeedbackDestination(); if (!octokit || !destination) throw new Error('GitHub comment integration is not configured.'); const category = destination.discussionCategories.nodes.find( (category) => category.name === DocsCategory, ); if (!category) throw new Error( `Please create a "${DocsCategory}" category in GitHub Discussion`, ); const title = `Feedback for ${url}`; const body = `[${feedback.opinion}] ${feedback.message}\n\n> Forwarded from user feedback.`; let { search: { nodes: [discussion], }, }: { search: { nodes: { id: string; url: string }[]; }; } = await octokit.graphql(` query { search(type: DISCUSSION, query: ${JSON.stringify(`${title} in:title repo:${owner}/${repo} author:@me`)}, first: 1) { nodes { ... on Discussion { id, url } } } }`); if (discussion) { await octokit.graphql(` mutation { addDiscussionComment(input: { body: ${JSON.stringify(body)}, discussionId: "${discussion.id}" }) { comment { id } } }`); } else { const result: { discussion: { id: string; url: string }; } = await octokit.graphql(` mutation { createDiscussion(input: { repositoryId: "${destination.id}", categoryId: "${category!.id}", body: ${JSON.stringify(body)}, title: ${JSON.stringify(title)} }) { discussion { id, url } } }`); discussion = result.discussion; } return { githubUrl: discussion.url, }; } ``` * Create your own GitHub App and obtain its app ID and private key. * Fill required environment variables. * Replace constants like `owner`, `repo`, and `DocsCategory`. # Fumadocs Framework: AI & LLMs URL: /docs/ui/llms Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(integrations)/llms.mdx Integrate AI functionality to Fumadocs. ## Docs for LLM You can make your docs site more AI-friendly with dedicated docs content for large language models. To begin, make a `getLLMText` function that converts pages into static MDX content. In **Fumadocs MDX**, you can do: ```ts title="lib/get-llm-text.ts" import { source } from '@/lib/source'; import type { InferPageType } from 'fumadocs-core/source'; export async function getLLMText(page: InferPageType) { const processed = await page.data.getText('processed'); return `# ${page.data.title} (${page.url}) ${processed}`; } ``` It requires `includeProcessedMarkdown` to be enabled: ```ts title="source.config.ts" import { defineDocs } from 'fumadocs-mdx/config'; export const docs = defineDocs({ docs: { // [!code ++:3] postprocess: { includeProcessedMarkdown: true, }, }, }); ``` ### `llms-full.txt` A version of docs for AIs to read. Next.js React Router Tanstack Start ```ts title="app/llms-full.txt/route.ts" import { source } from '@/lib/source'; import { getLLMText } from '@/lib/get-llm-text'; // cached forever export const revalidate = false; export async function GET() { const scan = source.getPages().map(getLLMText); const scanned = await Promise.all(scan); return new Response(scanned.join('\n\n')); } ``` ```ts title="app/routes.ts" import { index, route, type RouteConfig } from '@react-router/dev/routes'; export default [ // [!code ++] route('llms-full.txt', 'routes/llms-full.ts'), ] satisfies RouteConfig; ``` ```ts title="app/routes/llms-full.ts" import { source } from '@/lib/source'; import { getLLMText } from '@/lib/get-llm-text'; export async function loader() { const scan = source.getPages().map(getLLMText); const scanned = await Promise.all(scan); return new Response(scanned.join('\n\n')); } ``` ```ts title="src/routes/llms-full[.]txt.ts" import { createFileRoute } from '@tanstack/react-router'; import { source } from '@/lib/source'; import { getLLMText } from '@/lib/get-llm-text'; export const Route = createFileRoute('/llms-full.txt')({ server: { handlers: { GET: async () => { const scan = source.getPages().map(getLLMText); const scanned = await Promise.all(scan); return new Response(scanned.join('\n\n')); }, }, }, }); ``` ### `*.mdx` Allow AI agents to get the content of a page as Markdown/MDX, by appending `.mdx` to the end of path. Make a route handler to return page content, and a middleware to point to it: Next.js React Router Tanstack Start ```ts title="app/llms.mdx/[[...slug]]/route.ts" import { getLLMText } from '@/lib/get-llm-text'; import { source } from '@/lib/source'; import { notFound } from 'next/navigation'; export const revalidate = false; export async function GET( _req: Request, { params }: RouteContext<'/llms.mdx/[[...slug]]'>, ) { const { slug } = await params; const page = source.getPage(slug); if (!page) notFound(); return new Response(await getLLMText(page), { headers: { 'Content-Type': 'text/markdown', }, }); } export function generateStaticParams() { return source.generateParams(); } ``` ```ts title="next.config.ts" import type { NextConfig } from 'next'; const config: NextConfig = { // [!code ++:8] async rewrites() { return [ { source: '/docs/:path*.mdx', destination: '/llms.mdx/:path*', }, ]; }, }; ``` ```ts title="app/routes.ts" import { index, route, type RouteConfig } from '@react-router/dev/routes'; export default [ // [!code ++] route('llms.mdx/*', 'routes/llms-mdx.ts'), ] satisfies RouteConfig; ``` ```ts title="app/routes/llms-mdx.ts" import type { Route } from './+types/llms-mdx'; import { source } from '@/lib/source'; import { getLLMText } from '@/lib/get-llm-text'; export async function loader({ params }: Route.LoaderArgs) { const slugs = params['*'].split('/').filter((v) => v.length > 0); const page = source.getPage(slugs); if (!page) { return new Response('not found', { status: 404 }); } return new Response(await getLLMText(page), { headers: { 'Content-Type': 'text/markdown', }, }); } ``` ```ts title="app/root.tsx" import { rewritePath } from 'fumadocs-core/negotiation'; import type { Route } from './+types/root'; // [!code ++:20] const { rewrite: rewriteLLM } = rewritePath( '/docs{/*path}.mdx', '/llms.mdx{/*path}', ); const serverMiddleware: Route.MiddlewareFunction = async ( { request }, next, ) => { const url = new URL(request.url); const path = rewriteLLM(url.pathname); if (path) return Response.redirect(new URL(path, url)); return next(); }; export const middleware = [serverMiddleware]; ``` ```ts title="src/routes/llms[.]mdx.$.ts" import { createFileRoute, notFound } from '@tanstack/react-router'; import { source } from '@/lib/source'; export const Route = createFileRoute('/llms.mdx/$')({ server: { handlers: { GET: async ({ params }) => { const slugs = params._splat?.split('/') ?? []; const page = source.getPage(slugs); if (!page) throw notFound(); return new Response(await page.data.getText('raw'), { headers: { 'Content-Type': 'text/markdown', }, }); }, }, }, }); ``` ```ts title="src/start.ts" import { createMiddleware, createStart } from '@tanstack/react-start'; import { rewritePath } from 'fumadocs-core/negotiation'; import { redirect } from '@tanstack/react-router'; const { rewrite: rewriteLLM } = rewritePath( '/docs{/*path}.mdx', 'llms.mdx{/*path}', ); const llmMiddleware = createMiddleware().server(({ next, request }) => { const url = new URL(request.url); const path = rewriteLLM(url.pathname); if (path) { throw redirect(new URL(path, url)); } return next(); }); export const startInstance = createStart(() => { return { requestMiddleware: [llmMiddleware], }; }); ``` #### `Accept` To serve the Markdown content instead for AI agents, you can leverage the `Accept` header. ```ts title="proxy.ts (Next.js)" import { NextRequest, NextResponse } from 'next/server'; import { isMarkdownPreferred, rewritePath } from 'fumadocs-core/negotiation'; const { rewrite: rewriteLLM } = rewritePath('/docs/*path', '/llms.mdx/*path'); export default function proxy(request: NextRequest) { if (isMarkdownPreferred(request)) { const result = rewriteLLM(request.nextUrl.pathname); if (result) { return NextResponse.rewrite(new URL(result, request.nextUrl)); } } return NextResponse.next(); } ``` ### Page Actions Common page actions for AI, require [`*.mdx`](#mdx-extension) to be implemented first. AI Page Actions npm pnpm yarn bun ```bash npx @fumadocs/cli add ai/page-actions ``` ```bash pnpm dlx @fumadocs/cli add ai/page-actions ``` ```bash yarn dlx @fumadocs/cli add ai/page-actions ``` ```bash bun x @fumadocs/cli add ai/page-actions ``` Use it in your docs page like: ```tsx title="app/docs/[[...slug]]/page.tsx"
``` ## Ask AI AI Search You can install the AI search dialog using Fumadocs CLI: npm pnpm yarn bun ```bash npx @fumadocs/cli add ai/search ``` ```bash pnpm dlx @fumadocs/cli add ai/search ``` ```bash yarn dlx @fumadocs/cli add ai/search ``` ```bash bun x @fumadocs/cli add ai/search ``` Then, add the `` component to your root layout. ### AI Model By default, it's configured for [Inkeep AI](https://inkeep.com) using Vercel AI SDK. To setup for Inkeep AI: 1. Add your Inkeep API key to environment variables: ```dotenv INKEEP_API_KEY="..." ``` 2. Add the `AISearchTrigger` component to root layout (or anywhere you prefer): ```tsx import { AISearchTrigger } from '@/components/search'; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( {/* [!code ++] */} {children} ); } ``` To use your own AI models, update the configurations in `useChat` and `/api/chat` route. Note that Fumadocs doesn't provide the AI model, it's up to you. Your AI model can use the `llms-full.txt` file generated above, or more diversified sources of information when combined with 3rd party solutions. # Fumadocs Framework: next/og URL: /docs/ui/next-seo Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(integrations)/next-seo.mdx Usage with Next.js Metadata API. > Make sure to read their [Metadata section](https://nextjs.org/docs/app/building-your-application/optimizing/metadata) for the fundamentals of Metadata API. ## Metadata Image You can generate metadata images dynamically using `next/og`. Add the following under your loader, and define image metadata for pages: lib/source.ts app/docs/[[...slug]]/page.tsx ```ts import { type InferPageType } from 'fumadocs-core/source'; // [!code ++:8] export function getPageImage(page: InferPageType) { const segments = [...page.slugs, 'image.png']; return { segments, url: `/og/docs/${segments.join('/')}`, }; } ``` ```tsx import { notFound } from 'next/navigation'; import { source, getPageImage } from '@/lib/source'; import type { Metadata } from 'next'; export async function generateMetadata( props: PageProps<'/docs/[[...slug]]'>, ): Promise { const params = await props.params; const page = source.getPage(params.slug); if (!page) notFound(); return { title: page.data.title, description: page.data.description, openGraph: { // [!code ++] images: getPageImage(page).url, }, }; } ``` > We append `image.png` to the end of slugs so that we can access it via `/og/docs/my-page/image.png`. Finally, create a route handler to generate images at build time: ```tsx title="app/og/docs/[...slug]/route.tsx" import { getPageImage, source } from '@/lib/source'; import { notFound } from 'next/navigation'; import { ImageResponse } from 'next/og'; import { generate as DefaultImage } from 'fumadocs-ui/og'; export const revalidate = false; export async function GET( _req: Request, { params }: RouteContext<'/og/docs/[...slug]'>, ) { const { slug } = await params; const page = source.getPage(slug.slice(0, -1)); if (!page) notFound(); return new ImageResponse( ( ), { width: 1200, height: 630, }, ); } export function generateStaticParams() { return source.getPages().map((page) => ({ lang: page.locale, slug: getPageImage(page).segments, })); } ``` You can specify options for Satori (used by `next/og`), see [https://github.com/vercel/satori](https://github.com/vercel/satori) for reference. ### Other Presets There's other available styles on Fumadocs CLI, such as `mono`: npm pnpm yarn bun ```bash npx @fumadocs/cli@latest add og/mono ``` ```bash pnpm dlx @fumadocs/cli@latest add og/mono ``` ```bash yarn dlx @fumadocs/cli@latest add og/mono ``` ```bash bun x @fumadocs/cli@latest add og/mono ``` Replaced your old `generate` with the installed one. # Fumadocs Framework: RSS Feed URL: /docs/ui/rss Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(integrations)/rss.mdx Generate a RSS feed for your docs/blog. ## Setup You can create the feed object: ```ts title="lib/rss.ts" import { Feed } from 'feed'; import { source } from '@/lib/source'; const baseUrl = 'https://fumadocs.dev'; export function getRSS() { const feed = new Feed({ title: 'Fumadocs Blog', id: `${baseUrl}/blog`, link: `${baseUrl}/blog`, language: 'en', image: `${baseUrl}/banner.png`, favicon: `${baseUrl}/icon.png`, copyright: 'All rights reserved 2025, Fuma Nama', }); for (const page of source.getPages()) { feed.addItem({ id: page.url, title: page.data.title, description: page.data.description, link: `${baseUrl}${page.url}`, date: new Date(page.data.lastModified), author: [ { name: 'Fuma', }, ], }); } return feed.rss2(); } ``` And expose it using a server loader/route handler like: Next.js React Router Tanstack Start ```ts title="app/rss.xml/route.ts" import { getRSS } from '@/lib/rss'; export const revalidate = false; export function GET() { return new Response(getRSS()); } ``` ```ts // app/routes/rss.ts import { getRSS } from '@/lib/rss'; export async function loader() { return new Response(getRSS()); } // app/routes.ts import { route, type RouteConfig } from '@react-router/dev/routes'; export default [ // ... route('rss.xml', 'routes/rss.ts'), ] satisfies RouteConfig; ``` ```ts title="src/routes/rss[.]xml.ts" import { createFileRoute } from '@tanstack/react-router'; import { getRSS } from '@/lib/rss'; export const Route = createFileRoute('/rss.xml')({ server: { handlers: { GET: async () => new Response(getRSS()), }, }, }); ``` ### Next.js Metadata You can add an alternates object to the metadata object with your feed’s title and URL. ```ts title="app/layout.tsx" import type { Metadata } from 'next'; export const metadata: Metadata = { alternates: { types: { 'application/rss+xml': [ { title: 'Fumadocs Blog', url: 'https://fumadocs.dev/blog/index.xml', }, ], }, }, }; ``` # Fumadocs Framework: Validate Links URL: /docs/ui/validate-links Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(integrations)/validate-links.mdx Ensure your links are correct. ## Setup You can use [`next-validate-link`](https://next-validate-link.vercel.app) to validate your links in content files. > This guide is mainly for **Fumadocs MDX**, see the docs of `next-validate-link` for other setups. Create a script for Bun: ```ts title="scripts/lint.ts" import { type FileObject, printErrors, scanURLs, validateFiles, } from 'next-validate-link'; import type { InferPageType } from 'fumadocs-core/source'; import { source } from '@/lib/source'; async function checkLinks() { const scanned = await scanURLs({ // pick a preset for your React framework preset: 'next', populate: { 'docs/[[...slug]]': source.getPages().map((page) => { return { value: { slug: page.slugs, }, hashes: getHeadings(page), }; }), }, }); printErrors( await validateFiles(await getFiles(), { scanned, // check `href` attributes in different MDX components markdown: { components: { Card: { attributes: ['href'] }, }, }, // check relative paths checkRelativePaths: 'as-url', }), true, ); } function getHeadings({ data }: InferPageType): string[] { return data.toc.map((item) => item.url.slice(1)); } function getFiles() { const promises = source.getPages().map( async (page): Promise => ({ path: page.absolutePath, content: await page.data.getText('raw'), url: page.url, data: page.data, }), ); return Promise.all(promises); } void checkLinks(); ``` ### Running Lint To access the `source` object outside of app runtime, you'll need a runtime loader: bunfig.toml scripts/preload.ts ```toml preload = ["./scripts/preload.ts"] ``` ```ts import { createMdxPlugin } from 'fumadocs-mdx/bun'; Bun.plugin(createMdxPlugin()); ``` Run the script to validate links: ```bash bun ./scripts/lint.ts ``` # Fumadocs Framework: Themes URL: /docs/ui/theme Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(ui)/theme.mdx Add Theme to Fumadocs UI import { WidthTrigger } from './theme.client'; ## Overview Fumadocs UI adds its own colors, animations, and utilities with Tailwind CSS preset. ### Setup Only Tailwind CSS v4 is supported, the preset will also include source to Fumadocs UI itself: ```css title="Tailwind CSS" @import 'tailwindcss'; @import 'fumadocs-ui/css/neutral.css'; @import 'fumadocs-ui/css/preset.css'; ``` For Shadcn UI, you can use the `shadcn` preset to let Fumadocs UI to adopt your Shadcn UI theme. ```css @import 'tailwindcss'; @import 'fumadocs-ui/css/shadcn.css'; @import 'fumadocs-ui/css/preset.css'; ``` ### Preflight Changes By using the Tailwind CSS plugin, or the pre-built stylesheet, your default border, text and background colors will be changed. ### Light/Dark Modes Fumadocs supports light/dark modes with [`next-themes`](https://github.com/pacocoursey/next-themes), it is included in Root Provider. See [Root Provider](/docs/ui/layouts/root-provider#theme-provider) to learn more. ### RTL Layout RTL (Right-to-left) layout is supported. To enable RTL, set the `dir` prop to `rtl` in body and root provider (required for Radix UI). ```tsx import { RootProvider } from 'fumadocs-ui/provider/'; import type { ReactNode } from 'react'; export default function RootLayout({ children }: { children: ReactNode }) { return ( {children} ); } ``` ### Layout Width Customise the max width of docs layout with CSS Variables. ```css :root { --fd-layout-width: 1400px; } ``` ### Colors It comes with many themes out-of-the-box, you can pick one you prefer. ```css @import 'fumadocs-ui/css/.css'; @import 'fumadocs-ui/css/preset.css'; ``` Neutral Black Vitepress Dusk Catppuccin Ocean Purple Solar The design system was inspired by [Shadcn UI](https://ui.shadcn.com), you can also customize the colors using CSS variables. ```css title="global.css" @theme { --color-fd-background: hsl(0, 0%, 96%); --color-fd-foreground: hsl(0, 0%, 3.9%); --color-fd-muted: hsl(0, 0%, 96.1%); --color-fd-muted-foreground: hsl(0, 0%, 45.1%); --color-fd-popover: hsl(0, 0%, 98%); --color-fd-popover-foreground: hsl(0, 0%, 15.1%); --color-fd-card: hsl(0, 0%, 94.7%); --color-fd-card-foreground: hsl(0, 0%, 3.9%); --color-fd-border: hsla(0, 0%, 80%, 50%); --color-fd-primary: hsl(0, 0%, 9%); --color-fd-primary-foreground: hsl(0, 0%, 98%); --color-fd-secondary: hsl(0, 0%, 93.1%); --color-fd-secondary-foreground: hsl(0, 0%, 9%); --color-fd-accent: hsla(0, 0%, 82%, 50%); --color-fd-accent-foreground: hsl(0, 0%, 9%); --color-fd-ring: hsl(0, 0%, 63.9%); } .dark { --color-fd-background: hsl(0, 0%, 7.04%); --color-fd-foreground: hsl(0, 0%, 92%); --color-fd-muted: hsl(0, 0%, 12.9%); --color-fd-muted-foreground: hsla(0, 0%, 70%, 0.8); --color-fd-popover: hsl(0, 0%, 11.6%); --color-fd-popover-foreground: hsl(0, 0%, 86.9%); --color-fd-card: hsl(0, 0%, 9.8%); --color-fd-card-foreground: hsl(0, 0%, 98%); --color-fd-border: hsla(0, 0%, 40%, 20%); --color-fd-primary: hsl(0, 0%, 98%); --color-fd-primary-foreground: hsl(0, 0%, 9%); --color-fd-secondary: hsl(0, 0%, 12.9%); --color-fd-secondary-foreground: hsl(0, 0%, 92%); --color-fd-accent: hsla(0, 0%, 40.9%, 30%); --color-fd-accent-foreground: hsl(0, 0%, 90%); --color-fd-ring: hsl(0, 0%, 54.9%); } ``` ### Typography We have a built-in plugin forked from [Tailwind CSS Typography](https://tailwindcss.com/docs/typography-plugin). The plugin adds a `prose` class and variants to customise it. ```tsx

Good Heading

``` > The plugin works with and only with Fumadocs UI's MDX components, it may conflict with `@tailwindcss/typography`. > If you need to use `@tailwindcss/typography` over the default plugin, [set a class name option](https://github.com/tailwindlabs/tailwindcss-typography/blob/main/README.md#changing-the-default-class-name) to avoid conflicts. # Fumadocs Framework: Internationalization URL: /docs/ui/internationalization Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/internationalization/index.mdx Support multiple languages in your documentation ## 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](https://github.com/amannn/next-intl) on Next.js. # Fumadocs Framework: Next.js URL: /docs/ui/internationalization/next Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/internationalization/next.mdx Support i18n routing on your Next.js + Fumadocs app You can [learn more about i18n in Next.js](https://nextjs.org/docs/app/building-your-application/routing/internationalization). ## Setup Define the i18n configurations in a file, we will import it with `@/lib/i18n` in this guide. ```ts title="lib/i18n.ts" import { defineI18n } from 'fumadocs-core/i18n'; export const i18n = defineI18n({ defaultLanguage: 'en', languages: ['en', 'cn'], }); ``` > See [available options](/docs/headless/internationalization) for i18n config. ### Middleware Create a middleware that redirects users to appropriate locale. ```ts title="proxy.ts" import { createI18nMiddleware } from 'fumadocs-core/i18n/middleware'; import { i18n } from '@/lib/i18n'; export default createI18nMiddleware(i18n); export const config = { // Matcher ignoring `/_next/` and `/api/` // You may need to adjust it to ignore static assets in `/public` folder matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], }; ``` The default middleware is optional, you can also use your own middleware or the one provided by i18n libraries. Make sure its behaviour aligns with the [`hidePrefix`](/docs/headless/internationalization#hide-locale-prefix) option you set in your i18n config. ### Routing Create a `/app/[lang]` folder, and move your pages/layouts into it, except route handlers. Did you accidentally find your styles lost? Make sure the import path to `global.css` is still correct! Provide UI translations and other config to ``, the English translations are used when `translations` is not specified. ```tsx title="app/[lang]/layout.tsx" import { RootProvider } from 'fumadocs-ui/provider/next'; import { defineI18nUI } from 'fumadocs-ui/i18n'; import { i18n } from '@/lib/i18n'; // [!code ++:11] const { provider } = defineI18nUI(i18n, { translations: { en: { displayName: 'English', }, cn: { displayName: 'Chinese', search: '搜尋文檔', }, }, }); export default async function RootLayout({ params, children, }: { params: Promise<{ lang: string }>; children: React.ReactNode; }) { const lang = (await params).lang; return ( {children} ); } ``` ### Pass Locale Add `locale` parameter to `baseOptions()` and add `i18n` into it: ```tsx title="lib/layout.shared.tsx" import { i18n } from '@/lib/i18n'; import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; // [!code highlight] export function baseOptions(locale: string): BaseLayoutProps { return { i18n, // [!code ++] // different props based on `locale` }; } ``` Pass the locale to Fumadocs in your pages and layouts. lib/source.ts Home Layout Docs Layout Docs Page ```ts import { i18n } from '@/lib/i18n'; import { loader } from 'fumadocs-core/source'; export const source = loader({ i18n, // [!code ++] // other options }); ``` ```tsx title="app/[lang]/(home)/layout.tsx" import type { ReactNode } from 'react'; import { HomeLayout } from 'fumadocs-ui/layouts/home'; import { baseOptions } from '@/lib/layout.shared'; export default async function Layout({ params, children, }: { params: Promise<{ lang: string }>; children: ReactNode; }) { const { lang } = await params; return {children}; // [!code highlight] } ``` ```tsx title="app/[lang]/docs/layout.tsx" import type { ReactNode } from 'react'; import { source } from '@/lib/source'; import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import { baseOptions } from '@/lib/layout.shared'; export default async function Layout({ params, children, }: { params: Promise<{ lang: string }>; children: ReactNode; }) { const { lang } = await params; return ( // [!code highlight] {children} ); } ``` ```ts title="app/[lang]/docs/[[...slug]]/page.tsx" import { source } from '@/lib/source'; export default async function Page({ params, }: { params: Promise<{ lang: string; slug?: string[] }>; }) { const { slug, lang } = await params; // get page source.getPage(slug); // [!code --] source.getPage(slug, lang); // [!code ++] // get pages source.getPages(); // [!code --] source.getPages(lang); // [!code ++] } ``` Using another name for lang dynamic segment?}> If you're using another name like `app/[locale]`, you also need to update `generateStaticParams()` in docs page: ```tsx export function generateStaticParams() { return source.generateParams(); // [!code --] return source.generateParams('slug', 'locale'); // [!code ++] new param name } ``` ### Search Configure i18n on your search solution. * **Built-in Search (Orama):** See [Internationalization](/docs/headless/search/orama#internationalization). * **Cloud Solutions (e.g. Algolia):** They usually have official support for multilingual. ## Writing Documents See [i18n routing](/docs/ui/page-conventions#i18n-routing) to learn how to create pages for specific locales. ### Navigation Fumadocs only handles navigation for its own layouts (e.g. sidebar). For other places, you can use the `useParams` hook to get the locale from url, and attend it to `href`. ```tsx import Link from 'next/link'; import { useParams } from 'next/navigation'; const { lang } = useParams(); return This is a link; ``` In addition, the [`fumadocs-core/dynamic-link`](/docs/headless/components/link#dynamic-hrefs) component supports dynamic hrefs, you can use it to attend the locale prefix. It is useful for Markdown/MDX content. ```mdx title="content.mdx" import { DynamicLink } from 'fumadocs-core/dynamic-link'; This is a link ``` # Fumadocs Framework: React Router URL: /docs/ui/internationalization/react-router Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/internationalization/react-router.mdx Support i18n routing on your React Router + Fumadocs app. ## Setup Define the i18n configurations in a file, we will import it with `@/lib/i18n` in this guide. ```ts title="app/lib/i18n.ts" import { defineI18n } from 'fumadocs-core/i18n'; export const i18n = defineI18n({ defaultLanguage: 'en', languages: ['cn', 'en'], }); ``` > See [available options](/docs/headless/internationalization) for i18n config. ### Routing Add `:lang` prefix to all your pages. ```ts title="app/routes.ts" import { route, type RouteConfig } from '@react-router/dev/routes'; export default [ // [!code highlight:2] route(':lang', 'routes/home.tsx'), route(':lang/docs/*', 'docs/page.tsx'), route('api/search', 'docs/search.ts'), ] satisfies RouteConfig; ``` #### Make locale optional You can also use `:lang?` prefix, and update the i18n config: app/lib/i18n.ts app/routes.ts ```ts import { defineI18n } from 'fumadocs-core/i18n'; export const i18n = defineI18n({ defaultLanguage: 'en', languages: ['cn', 'en'], // [!code ++] hideLocale: 'default-locale', }); ``` ```ts import { route, type RouteConfig } from '@react-router/dev/routes'; export default [ // [!code highlight:2] route(':lang?', 'routes/home.tsx'), route(':lang?/docs/*', 'docs/page.tsx'), route('api/search', 'docs/search.ts'), ] satisfies RouteConfig; ``` ### Pages Provide UI translations and other config to ``, the English translations are used as fallbacks. ```tsx title="app/root.tsx" import { Links, Meta, Scripts, ScrollRestoration, useParams, } from 'react-router'; import { RootProvider } from 'fumadocs-ui/provider/base'; import { ReactRouterProvider } from 'fumadocs-core/framework/react-router'; import { defineI18nUI } from 'fumadocs-ui/i18n'; import { i18n } from '@/lib/i18n'; import './app.css'; // [!code ++:11] const { provider } = defineI18nUI(i18n, { translations: { en: { displayName: 'English', }, cn: { displayName: 'Chinese', search: '搜尋文檔', }, }, }); export function Layout({ children }: { children: React.ReactNode }) { const { lang = i18n.defaultLanguage } = useParams(); return ( {children} ); } ``` ### Pass Locale Add `locale` parameter to `baseOptions()` and include `i18n` in props: ```tsx title="app/lib/layout.shared.tsx" import { i18n } from '@/lib/i18n'; import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(locale: string): BaseLayoutProps { return { i18n, // [!code ++] // different props based on `locale` }; } ``` Pass the locale to Fumadocs in your pages and layouts. app/lib/source.ts Home Layout Docs Page ```ts import { i18n } from '@/lib/i18n'; import { loader } from 'fumadocs-core/source'; export const source = loader({ i18n, // [!code ++] // other options }); ``` ```tsx title="app/routes/home.tsx" import type { Route } from './+types/home'; import { HomeLayout } from 'fumadocs-ui/layouts/home'; import { baseOptions } from '@/lib/layout.shared'; export default function Home({ params }: Route.ComponentProps) { return ( // [!code highlight] ); } ``` ```tsx title="app/docs/page.tsx" import type { Route } from './+types/page'; import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import { source } from '@/lib/source'; import type * as PageTree from 'fumadocs-core/page-tree'; import { baseOptions } from '@/lib/layout.shared'; export async function loader({ params }: Route.LoaderArgs) { const slugs = params['*'].split('/').filter((v) => v.length > 0); // [!code --] const page = source.getPage(slugs); // [!code ++] const page = source.getPage(slugs, params.lang); if (!page) throw new Response('Not found', { status: 404 }); return { path: page.path, // [!code --] tree: source.pageTree, // [!code ++] tree: source.getPageTree(params.lang), }; } export default function Page({ loaderData, params }: Route.ComponentProps) { const { tree, path } = loaderData; return ( ); } ``` ### Search Configure i18n on your search solution. * **Built-in Search (Orama):** See [Internationalization](/docs/headless/search/orama#internationalization). * **Cloud Solutions (e.g. Algolia):** They usually have official support for multilingual. ## Writing Documents See [i18n routing](/docs/ui/page-conventions#i18n-routing) to learn how to create pages for specific locales. ### Navigation Fumadocs only handles navigation for its own layouts (e.g. sidebar). For other places, you can use the `useParams` hook to get the locale from url. ```tsx import { Link, useParams } from 'react-router'; const { lang } = useParams(); About Us; ``` In addition, the [`fumadocs-core/dynamic-link`](/docs/headless/components/link#dynamic-hrefs) component supports dynamic hrefs, you can use it to attend the locale prefix. It is useful for Markdown/MDX content. ```mdx title="content.mdx" import { DynamicLink } from 'fumadocs-core/dynamic-link'; This is a link ``` # Fumadocs Framework: Tanstack Start URL: /docs/ui/internationalization/tanstack-start Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/internationalization/tanstack-start.mdx Support i18n routing on your Tanstack Start + Fumadocs app. ## Setup Define the i18n configurations in a file, we will import it with `@/lib/i18n` in this guide. ```ts title="src/lib/i18n.ts" import { defineI18n } from 'fumadocs-core/i18n'; export const i18n = defineI18n({ defaultLanguage: 'en', languages: ['cn', 'en'], }); ``` > See [available options](/docs/headless/internationalization) for i18n config. ### Routing Create a `$lang` directory under `routes`, and move all pages into it. Provide UI translations and other config to ``, the English translations are used as fallbacks. ```tsx title="src/routes/__root.tsx" import { HeadContent, Scripts, useParams } from '@tanstack/react-router'; import * as React from 'react'; import { RootProvider } from 'fumadocs-ui/provider/base'; import { TanstackProvider } from 'fumadocs-core/framework/tanstack'; import { defineI18nUI } from 'fumadocs-ui/i18n'; import { i18n } from '@/lib/i18n'; // [!code ++:11] const { provider } = defineI18nUI(i18n, { translations: { cn: { displayName: 'Chinese', search: 'Translated Content', }, en: { displayName: 'English', }, }, }); function RootDocument({ children }: { children: React.ReactNode }) { const { lang } = useParams({ strict: false }); return ( {children} ); } ``` ### Pass Locale Add `locale` parameter to `baseOptions()` and include `i18n` in props: ```tsx title="src/lib/layout.shared.tsx" import { i18n } from '@/lib/i18n'; import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(locale: string): BaseLayoutProps { return { i18n, // [!code ++] // different props based on `locale` }; } ``` Pass the locale to Fumadocs in your pages and layouts. src/lib/source.ts Home Layout Docs Page ```ts import { i18n } from '@/lib/i18n'; import { loader } from 'fumadocs-core/source'; export const source = loader({ i18n, // [!code ++] // other options }); ``` ```tsx title="src/routes/$lang/index.tsx" import { createFileRoute } from '@tanstack/react-router'; import { HomeLayout } from 'fumadocs-ui/layouts/home'; import { baseOptions } from '@/lib/layout.shared'; export const Route = createFileRoute('/$lang/')({ component: Home, }); function Home() { const { lang } = Route.useParams(); // [!code highlight] return ; } ``` ```tsx title="src/routes/$lang/docs/$.tsx" import { createFileRoute, notFound } from '@tanstack/react-router'; import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import { createServerFn } from '@tanstack/react-start'; import { source } from '@/lib/source'; import { baseOptions } from '@/lib/layout.shared'; export const Route = createFileRoute('/$lang/docs/$')({ component: Page, loader: async ({ params }) => { const data = await loader({ // [!code ++:4] data: { slugs: params._splat?.split('/') ?? [], lang: params.lang, }, }); await clientLoader.preload(data.path); return data; }, }); // [!code highlight:13] const loader = createServerFn({ method: 'GET', }) .inputValidator((params: { slugs: string[]; lang?: string }) => params) .handler(async ({ data: { slugs, lang } }) => { const page = source.getPage(slugs, lang); if (!page) throw notFound(); return { tree: source.getPageTree(lang) as object, path: page.path, }; }); function Page() { const { lang } = Route.useParams(); // ... return ( // [!code highlight] ); } ``` ### Search Configure i18n on your search solution. * **Built-in Search (Orama):** See [Internationalization](/docs/headless/search/orama#internationalization). * **Cloud Solutions (e.g. Algolia):** They usually have official support for multilingual. ## Writing Documents See [i18n routing](/docs/ui/page-conventions#i18n-routing) to learn how to create pages for specific locales. ### Navigation Fumadocs only handles navigation for its own layouts (e.g. sidebar). For other places, you can use the `useParams` hook to get the locale from url. ```tsx import { Link, useParams } from '@tanstack/react-router'; const { lang } = useParams({ from: '/$lang/' }); Open Docs ; ``` In addition, the [`fumadocs-core/dynamic-link`](/docs/headless/components/link#dynamic-hrefs) component supports dynamic hrefs, you can use it to attend the locale prefix. It is useful for Markdown/MDX content. ```mdx title="content.mdx" import { DynamicLink } from 'fumadocs-core/dynamic-link'; This is a link ``` # Fumadocs Framework: Manual Installation URL: /docs/ui/manual-installation Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/manual-installation/index.mdx Add Fumadocs to existing projects. # Fumadocs Framework: Next.js URL: /docs/ui/manual-installation/next Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/manual-installation/next.mdx Setup Fumadocs on Next.js. ## Prerequisite Before continuing, make sure you have configured: * Next.js 15. * Tailwind CSS 4. We will use [Fumadocs MDX](/docs/mdx) as a content source, you can configure it first:
### Installation npm pnpm yarn bun ```bash npm i fumadocs-mdx @types/mdx ``` ```bash pnpm add fumadocs-mdx @types/mdx ``` ```bash yarn add fumadocs-mdx @types/mdx ``` ```bash bun add fumadocs-mdx @types/mdx ``` Create the configuration file: ```ts title="source.config.ts" import { defineDocs, defineConfig } from 'fumadocs-mdx/config'; export const docs = defineDocs({ dir: 'content/docs', }); export default defineConfig(); ``` Add the plugin to Next.js config: ```js title="next.config.mjs" import { createMDX } from 'fumadocs-mdx/next'; /** @type {import('next').NextConfig} */ const config = { reactStrictMode: true, }; // [!code ++:4] const withMDX = createMDX({ // customise the config file path // configPath: "source.config.ts" }); // [!code highlight] export default withMDX(config); ``` The Next.js config must be a `.mjs` file since Fumadocs is ESM-only. Setup an import alias (optional): ```json title="tsconfig.json" { "compilerOptions": { "paths": { // [!code ++] "@/.source": [".source"] } } } ``` ### Integrate with Fumadocs You can create a `lib/source.ts` file and obtain Fumadocs source from the `docs` collection output. ```ts title="lib/source.ts" import { docs } from '@/.source'; import { loader } from 'fumadocs-core/source'; export const source = loader({ baseUrl: '/docs', source: docs.toFumadocsSource(), }); ``` The `.source` folder will be generated when you run `next dev` or `next build`. ### Done You can now write content in `content/docs` folder.
Fumadocs also supports other content sources, including [Content Collections](/docs/headless/content-collections) and headless CMS. ## Getting Started npm pnpm yarn bun ```bash npm i fumadocs-ui fumadocs-core ``` ```bash pnpm add fumadocs-ui fumadocs-core ``` ```bash yarn add fumadocs-ui fumadocs-core ``` ```bash bun add fumadocs-ui fumadocs-core ``` ### Root Layout Wrap the entire application inside [Root Provider](/docs/ui/layouts/root-provider), and add required styles to `body`. ```tsx title="app/layout.tsx" import { RootProvider } from 'fumadocs-ui/provider/next'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return ( {children} ); } ``` ### Styles Add the following Tailwind CSS styles to `global.css`. ```css title="global.css" @import 'tailwindcss'; /* [!code ++:2] */ @import 'fumadocs-ui/css/neutral.css'; @import 'fumadocs-ui/css/preset.css'; ``` > It doesn't come with a default font, you may choose one from `next/font`. ### Routes Create a `lib/layout.shared.tsx` file to put the shared options for our layouts. ```tsx title="lib/layout.shared.tsx" import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(): BaseLayoutProps { return { nav: { title: 'My App', }, }; } ``` Create the following files & routes: mdx-components.tsx app/docs/layout.tsx app/docs/[[...slug]]/page.tsx app/api/search/route.ts ```tsx import defaultMdxComponents from 'fumadocs-ui/mdx'; import type { MDXComponents } from 'mdx/types'; export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultMdxComponents, ...components, }; } ``` ```tsx import { source } from '@/lib/source'; import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import { baseOptions } from '@/lib/layout.shared'; export default function Layout({ children }: LayoutProps<'/docs'>) { return ( {children} ); } ``` ```tsx import { source } from '@/lib/source'; import { DocsBody, DocsDescription, DocsPage, DocsTitle, } from 'fumadocs-ui/page'; import { notFound } from 'next/navigation'; import { getMDXComponents } from '@/mdx-components'; import type { Metadata } from 'next'; import { createRelativeLink } from 'fumadocs-ui/mdx'; export default async function Page(props: PageProps<'/docs/[[...slug]]'>) { const params = await props.params; const page = source.getPage(params.slug); if (!page) notFound(); const MDX = page.data.body; return ( {page.data.title} {page.data.description} ); } export async function generateStaticParams() { return source.generateParams(); } export async function generateMetadata( props: PageProps<'/docs/[[...slug]]'>, ): Promise { const params = await props.params; const page = source.getPage(params.slug); if (!page) notFound(); return { title: page.data.title, description: page.data.description, }; } ``` ```ts import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; export const { GET } = createFromSource(source, { // https://docs.orama.com/docs/orama-js/supported-languages language: 'english', }); ``` > The search is powered by Orama, learn more about [Document Search](/docs/headless/search). ### Finish You can start the dev server and create MDX files. ```mdx title="content/docs/index.mdx" --- title: Hello World --- ## Introduction I love Anime. ``` # Fumadocs Framework: React Router URL: /docs/ui/manual-installation/react-router Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/manual-installation/react-router.mdx Setup Fumadocs on React Router. ## Getting Started Before continuing, make sure to configure: * Tailwind CSS 4. * Fumadocs MDX: follow the [setup guide](/docs/mdx/vite/react-router) and create essential files like `lib/source.ts`. ### Installation npm pnpm yarn bun ```bash npm i fumadocs-core fumadocs-ui ``` ```bash pnpm add fumadocs-core fumadocs-ui ``` ```bash yarn add fumadocs-core fumadocs-ui ``` ```bash bun add fumadocs-core fumadocs-ui ``` ### Styles Add the following to your Tailwind CSS file: ```css title="app/app.css" @import 'tailwindcss'; /* [!code ++:2] */ @import 'fumadocs-ui/css/neutral.css'; @import 'fumadocs-ui/css/preset.css'; ``` ### Create Pages Update your routes: ```ts title="routes.ts" import { type RouteConfig, index, route } from '@react-router/dev/routes'; export default [ index('routes/home.tsx'), // [!code ++:2] route('docs/*', 'docs/page.tsx'), route('api/search', 'docs/search.ts'), ] satisfies RouteConfig; ``` Create the following files: app/lib/layout.shared.tsx app/docs/page.tsx app/docs/search.ts ```tsx import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(): BaseLayoutProps { return { nav: { title: 'React Router', }, }; } ``` ```tsx import type { Route } from './+types/page'; import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import { DocsBody, DocsDescription, DocsPage, DocsTitle, } from 'fumadocs-ui/page'; import { source } from '@/lib/source'; import type * as PageTree from 'fumadocs-core/page-tree'; import defaultMdxComponents from 'fumadocs-ui/mdx'; import { docs } from '@/.source'; import { toClientRenderer } from 'fumadocs-mdx/runtime/vite'; import { baseOptions } from '@/lib/layout.shared'; export async function loader({ params }: Route.LoaderArgs) { const slugs = params['*'].split('/').filter((v) => v.length > 0); const page = source.getPage(slugs); if (!page) throw new Response('Not found', { status: 404 }); return { path: page.path, tree: source.getPageTree(), }; } const renderer = toClientRenderer( docs.doc, ({ toc, default: Mdx, frontmatter }) => { return ( {frontmatter.title} {frontmatter.title} {frontmatter.description} ); }, ); export default function Page({ loaderData }: Route.ComponentProps) { const { tree, path } = loaderData; const Content = renderer[path]; return ( ); } ``` ```ts import type { Route } from './+types/search'; import { createFromSource } from 'fumadocs-core/search/server'; import { source } from '@/lib/source'; const server = createFromSource(source, { // https://docs.orama.com/docs/orama-js/supported-languages language: 'english', }); export async function loader({ request }: Route.LoaderArgs) { return server.GET(request); } ``` Wrap your entire app under Fumadocs providers: ```tsx title="root.tsx" import { Links, Meta, Scripts, ScrollRestoration } from 'react-router'; import { RootProvider } from 'fumadocs-ui/provider/react-router'; import './app.css'; export function Layout({ children }: { children: React.ReactNode }) { return ( {/* [!code ++] */} {children} ); } ``` ### Done You can start writing documents at `content/docs`: ```mdx title="content/docs/index.mdx" --- title: Hello World --- I love Fumadocs ``` # Fumadocs Framework: Tanstack Start URL: /docs/ui/manual-installation/tanstack-start Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/manual-installation/tanstack-start.mdx Setup Fumadocs on Tanstack Start. ## Getting Started Before continuing, make sure to configure: * Tailwind CSS 4. * Fumadocs MDX: follow the [setup guide](/docs/mdx/vite/tanstack) and create essential files like `lib/source.ts`. ### Installation npm pnpm yarn bun ```bash npm i fumadocs-core fumadocs-ui ``` ```bash pnpm add fumadocs-core fumadocs-ui ``` ```bash yarn add fumadocs-core fumadocs-ui ``` ```bash bun add fumadocs-core fumadocs-ui ``` ### Styles Add the following to your Tailwind CSS file: ```css title="styles/app.css" @import 'tailwindcss'; /* [!code ++:2] */ @import 'fumadocs-ui/css/neutral.css'; @import 'fumadocs-ui/css/preset.css'; ``` ### Create Pages Create the following routes: lib/layout.shared.tsx routes/docs/$.tsx routes/api/search.ts ```tsx import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(): BaseLayoutProps { return { nav: { title: 'Tanstack Start', }, }; } ``` ```tsx import { createFileRoute, notFound } from '@tanstack/react-router'; import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import { createServerFn } from '@tanstack/react-start'; import { source } from '@/lib/source'; import type * as PageTree from 'fumadocs-core/page-tree'; import { useMemo } from 'react'; import { docs } from '@/.source'; import { DocsBody, DocsDescription, DocsPage, DocsTitle, } from 'fumadocs-ui/page'; import defaultMdxComponents from 'fumadocs-ui/mdx'; import { createClientLoader } from 'fumadocs-mdx/runtime/vite'; import { baseOptions } from '@/lib/layout.shared'; export const Route = createFileRoute('/docs/$')({ component: Page, loader: async ({ params }) => { const slugs = params._splat?.split('/') ?? []; const data = await loader({ data: slugs }); await clientLoader.preload(data.path); return data; }, }); const loader = createServerFn({ method: 'GET', }) .inputValidator((slugs: string[]) => slugs) .handler(async ({ data: slugs }) => { const page = source.getPage(slugs); if (!page) throw notFound(); return { tree: source.pageTree as object, path: page.path, }; }); const clientLoader = createClientLoader(docs.doc, { id: 'docs', component({ toc, frontmatter, default: MDX }) { return ( {frontmatter.title} {frontmatter.description} ); }, }); function Page() { const data = Route.useLoaderData(); const Content = clientLoader.getComponent(data.path); const tree = useMemo( () => transformPageTree(data.tree as PageTree.Folder), [data.tree], ); return ( ); } function transformPageTree(tree: PageTree.Folder): PageTree.Folder { function transform(item: T) { if (typeof item.icon !== 'string') return item; return { ...item, icon: ( ), }; } return { ...tree, index: tree.index ? transform(tree.index) : undefined, children: tree.children.map((item) => { if (item.type === 'folder') return transformPageTree(item); return transform(item); }), }; } ``` ```ts import { createFileRoute } from '@tanstack/react-router'; import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; const server = createFromSource(source, { // https://docs.orama.com/docs/orama-js/supported-languages language: 'english', }); export const Route = createFileRoute('/api/search')({ server: { handlers: { GET: async ({ request }) => server.GET(request), }, }, }); ``` Wrap your entire app under Fumadocs providers: ```tsx title="__root.tsx" import { createRootRoute, HeadContent, Outlet, Scripts, } from '@tanstack/react-router'; import * as React from 'react'; import { RootProvider } from 'fumadocs-ui/provider/tanstack'; export const Route = createRootRoute({ component: RootComponent, }); function RootComponent() { return ( ); } function RootDocument({ children }: { children: React.ReactNode }) { return ( {/* [!code ++] */} {children} ); } ``` ### Done You can start writing documents at `content/docs`: ```mdx title="content/docs/index.mdx" --- title: Hello World --- I love Fumadocs ``` # Fumadocs Framework: Waku URL: /docs/ui/manual-installation/waku Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/manual-installation/waku.mdx Setup Fumadocs on Waku. ## Getting Started Before continuing, make sure to configure: * Tailwind CSS 4. * Fumadocs MDX: follow the [setup guide](/docs/mdx/vite/waku) and create essential files like `lib/source.ts`. ### Installation npm pnpm yarn bun ```bash npm i fumadocs-core fumadocs-ui ``` ```bash pnpm add fumadocs-core fumadocs-ui ``` ```bash yarn add fumadocs-core fumadocs-ui ``` ```bash bun add fumadocs-core fumadocs-ui ``` ### Styles Add the following to your Tailwind CSS file: ```css title="src/styles/globals.css" @import 'tailwindcss'; /* [!code ++:2] */ @import 'fumadocs-ui/css/neutral.css'; @import 'fumadocs-ui/css/preset.css'; ``` ### Create Pages Create a file for sharing layout props: ```tsx title="src/lib/layout.shared.tsx" import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(): BaseLayoutProps { return { nav: { title: 'Waku', }, }; } ``` Create the following routes: src/pages/docs/_layout.tsx src/pages/docs/[...slugs].tsx src/pages/api/search.ts ```tsx import type { ReactNode } from 'react'; import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import { source } from '@/lib/source'; import { baseOptions } from '@/lib/layout.shared'; export default function Layout({ children }: { children: ReactNode }) { return ( {children} ); } ``` ```tsx import { source } from '@/lib/source'; import { PageProps } from 'waku/router'; import defaultMdxComponents from 'fumadocs-ui/mdx'; import { DocsBody, DocsDescription, DocsPage, DocsTitle, } from 'fumadocs-ui/page'; export default function DocPage({ slugs }: PageProps<'/docs/[...slugs]'>) { const page = source.getPage(slugs); if (!page) { return (

Page Not Found

The page you are looking for does not exist.

); } const MDX = page.data.body; return ( {page.data.title} {page.data.description} ); } export async function getConfig() { const pages = source .generateParams() .map((item) => (item.lang ? [item.lang, ...item.slug] : item.slug)); return { render: 'static' as const, staticPaths: pages, } as const; } ```
```ts import { createFromSource } from 'fumadocs-core/search/server'; import { source } from '@/lib/source'; export const { GET } = createFromSource(source); ```
Finally, wrap your entire app under Fumadocs providers: src/components/provider.tsx src/pages/_layout.tsx ```tsx 'use client'; import type { ReactNode } from 'react'; import { RootProvider } from 'fumadocs-ui/provider/waku'; export function Provider({ children }: { children: ReactNode }) { return {children}; } ``` ```tsx import '@/styles/globals.css'; import type { ReactNode } from 'react'; import { Provider } from '@/components/provider'; // [!code ++:3] export default function RootLayout({ children }: { children: ReactNode }) { return {children}; } export const getConfig = async () => { return { render: 'static', }; }; ``` ### Done You can start writing documents at `content/docs`: ```mdx title="content/docs/index.mdx" --- title: Hello World --- I love Fumadocs ``` # Fumadocs Framework: Markdown URL: /docs/ui/markdown Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/markdown/index.mdx How to write documents ## 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 renderers such as headless CMS. ## 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](https://mdxjs.com/docs/what-is-mdx/#mdx-syntax). * GFM (GitHub Flavored Markdown) is also supported, see [GFM Specification](https://github.github.com/gfm). ```mdx import { Component } from './component'; ## Heading ### Heading #### Heading Hello World, **Bold**, _Italic_, ~~Hidden~~ 1. First 2. Second 3. Third - Item 1 - Item 2 > Quote here ![alt](/image.png) | Table | Description | | ----- | ----------- | | Hello | World | ``` ### Frontmatter Fumadocs support YAML-based frontmatter by default, `title` represents the page title (`h1`) in Fumadocs UI. ```mdx --- title: This is a document --- ... ``` You can use the [`schema`](/docs/mdx/collections#schema-1) option to add frontmatter properties. ### Auto Links Internal links use the `` 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. ```mdx [My Link](https://github.github.com/gfm) This also works: https://github.github.com/gfm. ``` ### Cards Useful for adding links. ```mdx import { HomeIcon } from 'lucide-react'; Learn more about caching in Next.js Learn more about `fetch` in Next.js. } href="/" title="Home"> You can include icons too. ``` Learn more about caching in Next.js Learn more about `fetch` in Next.js. } href="/" title="Home"> You can include icons too. #### "Further Reading" Section You can do something like: ```tsx title="page.tsx" import { getPageTreePeers } from 'fumadocs-core/page-tree'; import { source } from '@/lib/source'; {getPageTreePeers(source.pageTree, '/docs/my-page').map((peer) => ( {peer.description} ))} ; ``` 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`/`warning` * `error` * `success` ```mdx Hello World Hello World Hello World ``` Hello World Hello World Hello World ### Headings An anchor is automatically applied to each heading, it sanitizes invalid characters like spaces. (e.g. `Hello World` to `hello-world`) ```md # Hello `World` ``` #### TOC Settings The table of contents (TOC) will be generated based on headings, you can also customise the effects of headings: ```md # 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: ``` #### Custom Anchor You can add `[#slug]` to customise heading anchors. ```md # heading [#my-heading-id] ``` You can also chain it with TOC settings like: ```md # 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](/docs/headless/mdx/rehype-code). ````mdx ```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. Input Output ````mdx ```ts twoslash lineNumbers const a = 'Hello World'; // ^? console.log(a); // [!code highlight] ``` ```` ```ts twoslash lineNumbers const a = 'Hello World'; // ^? console.log(a); // [!code highlight] ``` You can set the initial value of line numbers. Input Output ````mdx ```js lineNumbers=4 function main() { console.log('starts from 4'); return 0; } ``` ```` ```js lineNumbers=4 function main() { console.log('starts from 4'); return 0; } ``` #### Shiki Transformers We support some of the [Shiki Transformers](https://shiki.style/packages/transformers), allowing you to highlight/style specific lines. Input Output ````md ```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] ``` ````
```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] ```
#### Tab Groups ````mdx ```ts tab="Tab 1" console.log('A'); ``` ```ts tab="Tab 2" console.log('B'); ``` ```` Tab 1 Tab 2 ```ts console.log('A'); ``` ```ts console.log('B'); ``` While disabled by default, you use MDX in tab values by configuring the remark plugin: Fumadocs MDX MDX Compiler ```ts title="source.config.ts" import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { remarkCodeTabOptions: { parseMdx: true, // [!code ++] }, }, }); ``` ```ts import { compile } from '@mdx-js/mdx'; import { remarkCodeTab } from 'fumadocs-core/mdx-plugins'; await compile('...', { remarkPlugins: [ [ remarkCodeTab, { parseMdx: true, // [!code ++] }, ], ], }); ``` ````mdx ```ts tab=" Tab 1" console.log('A'); ``` ```ts tab=" Tab 2" console.log('B'); ``` ```` Tab 1 Tab 2 ```ts console.log('A'); ``` ```ts 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). ```mdx title="page.mdx" ./another.mdx ``` See [other usages of include](/docs/mdx/include). ### NPM Commands Useful for generating commands for installing packages with different package managers. Input Output ````md ```npm npm i next -D ``` ```` npm pnpm yarn bun ```bash npm i next -D ``` ```bash pnpm add next -D ``` ```bash yarn add next --dev ``` ```bash bun add next --dev ``` When using Fumadocs MDX, you can customise it like: ```tsx title="source.config.ts" import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { remarkNpmOptions: { // ... }, }, }); ``` ### Other Components You can configure & use the [built-in components](/docs/ui/components) in your MDX document, such as Tabs, Accordions and Zoomable Image. ## Additional Features You may be interested: # Fumadocs Framework: Math URL: /docs/ui/markdown/math Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/markdown/math.mdx Writing math equations in Markdown/MDX. ## Getting Started npm pnpm yarn bun ```bash npm install remark-math rehype-katex katex ``` ```bash pnpm add remark-math rehype-katex katex ``` ```bash yarn add remark-math rehype-katex katex ``` ```bash bun add remark-math rehype-katex katex ``` ### Add Plugins Add the required remark/rehype plugins, the code might be vary depending on your content source. Fumadocs MDX ```ts title="source.config.ts" import rehypeKatex from 'rehype-katex'; import remarkMath from 'remark-math'; import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { remarkPlugins: [remarkMath], // Place it at first, it should be executed before the syntax highlighter rehypePlugins: (v) => [rehypeKatex, ...v], }, }); ``` ### Add Stylesheet Add the following to root layout to make it looks great: ```tsx title="layout.tsx" import 'katex/dist/katex.css'; ``` ### Done Type some TeX expression in your documents, like the Pythagoras theorem: ````mdx Inline: $$c = \pm\sqrt{a^2 + b^2}$$ ```math c = \pm\sqrt{a^2 + b^2} ``` ```` Inline: $c = \pm\sqrt{a^2 + b^2}$ ```math c = \pm\sqrt{a^2 + b^2} ``` Taylor Expansion (expressing holomorphic function $f(x)$ in power series): ```math \displaystyle {\begin{aligned}T_{f}(z)&=\sum _{k=0}^{\infty }{\frac {(z-c)^{k}}{2\pi i}}\int _{\gamma }{\frac {f(w)}{(w-c)^{k+1}}}\,dw\\&={\frac {1}{2\pi i}}\int _{\gamma }{\frac {f(w)}{w-c}}\sum _{k=0}^{\infty }\left({\frac {z-c}{w-c}}\right)^{k}\,dw\\&={\frac {1}{2\pi i}}\int _{\gamma }{\frac {f(w)}{w-c}}\left({\frac {1}{1-{\frac {z-c}{w-c}}}}\right)\,dw\\&={\frac {1}{2\pi i}}\int _{\gamma }{\frac {f(w)}{w-z}}\,dw=f(z),\end{aligned}} ``` You can actually copy equations on Wikipedia, they will be converted into a KaTeX string when you paste it. ```math \displaystyle S[{\boldsymbol {q}}]=\int _{a}^{b}L(t,{\boldsymbol {q}}(t),{\dot {\boldsymbol {q}}}(t))\,dt. ``` # Fumadocs Framework: Mermaid URL: /docs/ui/markdown/mermaid Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/markdown/mermaid.mdx Rendering diagrams in your docs Fumadocs doesn't have a built-in Mermaid wrapper provided, we recommend using `mermaid` directly. ## Setup Install the required dependencies, `next-themes` is used with Fumadocs to manage the light/dark mode. npm pnpm yarn bun ```bash npm install mermaid next-themes ``` ```bash pnpm add mermaid next-themes ``` ```bash yarn add mermaid next-themes ``` ```bash bun add mermaid next-themes ``` Create the Mermaid component: ```tsx title="components/mdx/mermaid.tsx" 'use client'; import { use, useEffect, useId, useState } from 'react'; import { useTheme } from 'next-themes'; export function Mermaid({ chart }: { chart: string }) { const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); if (!mounted) return; return ; } const cache = new Map>(); function cachePromise( key: string, setPromise: () => Promise, ): Promise { const cached = cache.get(key); if (cached) return cached as Promise; const promise = setPromise(); cache.set(key, promise); return promise; } function MermaidContent({ chart }: { chart: string }) { const id = useId(); const { resolvedTheme } = useTheme(); const { default: mermaid } = use( cachePromise('mermaid', () => import('mermaid')), ); mermaid.initialize({ startOnLoad: false, securityLevel: 'loose', fontFamily: 'inherit', themeCSS: 'margin: 1.5rem auto 0;', theme: resolvedTheme === 'dark' ? 'dark' : 'default', }); const { svg, bindFunctions } = use( cachePromise(`${chart}-${resolvedTheme}`, () => { return mermaid.render(id, chart.replaceAll('\\n', '\n')); }), ); return (
{ if (container) bindFunctions?.(container); }} dangerouslySetInnerHTML={{ __html: svg }} /> ); } ``` > This is originally inspired by [remark-mermaid](https://github.com/the-guild-org/docs/blob/main/packages/remark-mermaid/src/mermaid.tsx). Add the component as a MDX component: ```tsx title="mdx-components.tsx" import defaultMdxComponents from 'fumadocs-ui/mdx'; import { Mermaid } from '@/components/mdx/mermaid'; import type { MDXComponents } from 'mdx/types'; export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultMdxComponents, Mermaid, // [!code ++] ...components, }; } ``` ## Usage Use it in MDX files. ```mdx ``` ### As CodeBlock You can convert `mermaid` codeblocks into the MDX usage with the `remarkMdxMermaid` remark plugin. Fumadocs MDX ```tsx title="source.config.ts" import { remarkMdxMermaid } from 'fumadocs-core/mdx-plugins'; import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { remarkPlugins: [remarkMdxMermaid], }, }); ``` ````md ```mermaid graph TD; A-->B; A-->C; ``` ```` # Fumadocs Framework: Twoslash URL: /docs/ui/markdown/twoslash Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/markdown/twoslash.mdx Use Typescript Twoslash in your docs ## Usage Thanks to the Twoslash integration of [Shiki](https://github.com/shikijs/shiki), the default code syntax highlighter, it is as simple as adding a transformer. npm pnpm yarn bun ```bash npm install fumadocs-twoslash twoslash ``` ```bash pnpm add fumadocs-twoslash twoslash ``` ```bash yarn add fumadocs-twoslash twoslash ``` ```bash bun add fumadocs-twoslash twoslash ``` For Next.js, you need to externalize the following deps: ```js title="next.config.mjs (Next.js)" const config = { reactStrictMode: true, // [!code ++] serverExternalPackages: ['typescript', 'twoslash'], }; ``` Add to your Shiki transformers. ```ts twoslash title="source.config.ts (Fumadocs MDX)" import { defineConfig } from 'fumadocs-mdx/config'; import { transformerTwoslash } from 'fumadocs-twoslash'; import { rehypeCodeDefaultOptions } from 'fumadocs-core/mdx-plugins'; export default defineConfig({ mdxOptions: { rehypeCodeOptions: { themes: { light: 'github-light', dark: 'github-dark', }, transformers: [ ...(rehypeCodeDefaultOptions.transformers ?? []), transformerTwoslash(), ], }, }, }); ``` Add styles, Tailwind CSS v4 is required. ```css title="Tailwind CSS" @import 'fumadocs-twoslash/twoslash.css'; ``` Add MDX components. ```tsx title="mdx-components.tsx" import * as Twoslash from 'fumadocs-twoslash/ui'; import defaultComponents from 'fumadocs-ui/mdx'; import type { MDXComponents } from 'mdx/types'; export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultComponents, ...Twoslash, ...components, }; } ``` Now you can add `twoslash` meta string to codeblocks. ````md ```ts twoslash console.log('Hello World'); ``` ```` ### Example Learn more about [Twoslash notations](https://twoslash.netlify.app/refs/notations). ```ts twoslash title="Test" lineNumbers type Player = { /** * The player name * @default 'user' */ name: string; }; // ---cut--- // @noErrors console.g; // ^| const player: Player = { name: 'Hello World' }; // ^? ``` ```ts twoslash const a = '123'; console.log(a); // ^^^ ``` ```ts twoslash import { generateFiles } from 'fumadocs-openapi'; void generateFiles({ input: ['./museum.yaml'], output: './content/docs/ui', }); ``` ```ts twoslash // @errors: 2588 const a = '123'; a = 132; ``` ## Cache You can enable filesystem cache with `typesCache` option: ```ts twoslash title="source.config.ts" import { transformerTwoslash } from 'fumadocs-twoslash'; import { createFileSystemTypesCache } from 'fumadocs-twoslash/cache-fs'; transformerTwoslash({ typesCache: createFileSystemTypesCache(), }); ``` # Fumadocs Framework: Navigation URL: /docs/ui/navigation Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/navigation/index.mdx Configure navigation in your Fumadocs app. # Fumadocs Framework: Layout Links URL: /docs/ui/navigation/links Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/navigation/links.mdx Customise the shared navigation links on all layouts. ## Overview Fumadocs allows adding additional links to your layouts with a `links` prop, like linking to your "showcase" page.
<> Nav <> Nav
Shared Options Docs Layout Home Layout ```tsx title="lib/layout.shared.tsx" import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(): BaseLayoutProps { return { links: [], // [!code highlight] // other options} }; } ``` ```tsx title="app/docs/layout.tsx" 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 ( {children} ); } ``` ```tsx title="app/(home)/layout.tsx" 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 ( {children} ); } ``` You can see all supported items below: ### Link Item A link to navigate to a URL/href, can be external. ```tsx title="lib/layout.shared.tsx" import { BookIcon } from 'lucide-react'; import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(): BaseLayoutProps { return { links: [ { icon: , text: 'Blog', url: '/blog', // secondary items will be displayed differently on navbar secondary: false, }, ], }; } ``` #### Active Mode The conditions to be marked as active. | Mode | Description | | ------------ | ----------------------------------------------------------- | | `url` | When browsing the specified url | | `nested-url` | When browsing the url and its child pages like `/blog/post` | | `none` | Never be active | ```tsx title="lib/layout.shared.tsx" import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(): BaseLayoutProps { return { links: [ { text: 'Blog', url: '/blog', active: 'nested-url', }, ], }; } ``` ### Icon Item Same as link item, but is shown as an icon button. Icon items are secondary by default. ```tsx title="lib/layout.shared.tsx" import { BookIcon } from 'lucide-react'; import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(): BaseLayoutProps { return { links: [ { type: 'icon', label: 'Visit Blog', // `aria-label` icon: , text: 'Blog', url: '/blog', }, ], }; } ``` ### Custom Item Display a custom component. ```tsx title="lib/layout.shared.tsx" import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(): BaseLayoutProps { return { links: [ { type: 'custom', children: , secondary: true, }, ], }; } ``` ### GitHub URL There's also a shortcut for adding GitHub repository link item. ```tsx twoslash title="lib/layout.shared.tsx" import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(): BaseLayoutProps { return { githubUrl: 'https://github.com', }; } ``` ### Normal Menu A menu containing multiple link items. ```tsx title="lib/layout.shared.tsx" import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(): BaseLayoutProps { return { links: [ { type: 'menu', text: 'Guide', items: [ { text: 'Getting Started', description: 'Learn to use Fumadocs', url: '/docs', }, ], }, ], }; } ``` ### Navigation Menu In Home Layout, you can add navigation menu (fully animated) to the navbar. Nav ```tsx title="app/(home)/layout.tsx" import { baseOptions } from '@/lib/layout.shared'; import type { ReactNode } from 'react'; import { HomeLayout } from 'fumadocs-ui/layouts/home'; import { NavbarMenu, NavbarMenuContent, NavbarMenuLink, NavbarMenuTrigger, } from 'fumadocs-ui/layouts/home/navbar'; export default function Layout({ children }: { children: ReactNode }) { return ( Documentation Hello World ), }, // other items ]} > {children} ); } ``` # Fumadocs Framework: Sidebar Links URL: /docs/ui/navigation/sidebar Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/navigation/sidebar.mdx Customise sidebar navigation links on Docs Layout. ## Overview
Sidebar
Sidebar items are rendered from the page tree you passed to ``. For `source.pageTree`, it generates the tree from your file structure, see [available patterns](/docs/ui/page-conventions). ```tsx title="layout.tsx" import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import { source } from '@/lib/source'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return ( {children} ); } ``` You may hardcode it too: ```tsx title="layout.tsx" import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return ( {children} ); } ``` ### Sidebar Tabs (Dropdown) Sidebar Tabs are folders with tab-like behaviours, only the content of opened tab will be visible.
Sidebar Tabs
By default, the tab trigger will be displayed as a **Dropdown** component (hidden unless one of its items is active). You can add items by marking folders as [Root Folders](/docs/ui/page-conventions#root-folder), create a `meta.json` file in the folder: ```json title="content/docs/my-folder/meta.json" { "title": "Name of Folder", "description": "The description of root folder (optional)", "root": true } ``` Or specify them explicitly: ```tsx title="/app/docs/layout.tsx" import { DocsLayout } from 'fumadocs-ui/layouts/docs'; ; ``` Set it to `false` to disable: ```tsx import { DocsLayout } from 'fumadocs-ui/layouts/docs'; ; ``` You can specify a `banner` to the [Docs Layout](/docs/ui/layouts/docs) component. ```tsx import { DocsLayout, type DocsLayoutProps } from 'fumadocs-ui/layouts/docs'; import type { ReactNode } from 'react'; import { baseOptions } from '@/lib/layout.shared'; import { source } from '@/lib/source'; const docsOptions: DocsLayoutProps = { ...baseOptions(), tree: source.pageTree, sidebar: { banner:
Hello World
, }, }; export default function Layout({ children }: { children: ReactNode }) { return {children}; } ```
#### Decoration Change the icon/styles of tabs. ```tsx import { DocsLayout } from 'fumadocs-ui/layouts/docs'; ({ ...option, icon: , }), }, }} />; ``` # Fumadocs Framework: Configurations URL: /docs/ui/openapi/configurations Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/openapi/configurations.mdx Customise Fumadocs OpenAPI ## File Generator Pass options to the `generateFiles` function. ### Input * OpenAPI Server object. * anything supported by [`input`](#input-1). ```ts import { generateFiles } from 'fumadocs-openapi'; void generateFiles({ input: ['./unkey.json'], }); ``` ### Output The output directory. ```ts import { generateFiles } from 'fumadocs-openapi'; void generateFiles({ output: '/content/docs', }); ``` ### Per Customise how the page is generated, default to `operation`. Operation refers to an API endpoint with specific method like `/api/weather:GET`. | mode | | output | | --------- | --------------------------- | ------------------------------------- | | tag | operations with same tag | `{tag_name}.mdx` | | file | operations in schema schema | `{file_name}.mdx` | | operation | each operation | `{operationId ?? endpoint_path}.mdx`) | ```ts import { generateFiles } from 'fumadocs-openapi'; void generateFiles({ per: 'tag', }); ``` ### Group By In `operation` mode, you can group output files with folders. | value | output | | ----- | ------------------------------------------------------ | | tag | `{tag}/{operationId ?? endpoint_path}.mdx` | | route | `{endpoint_path}/{method}.mdx` (ignores `name` option) | | none | `{operationId ?? endpoint_path}.mdx` (default) | ```ts import { generateFiles } from 'fumadocs-openapi'; void generateFiles({ per: 'operation', groupBy: 'tag', }); ``` ### Index Generate index files with cards linking to generated pages. ```ts import { generateFiles } from 'fumadocs-openapi'; void generateFiles({ input: ['./petstore.yaml', './museum.yaml'], output: './content/docs', index: { // for generating `href` url: { baseUrl: '/docs', contentDir: './content/docs', }, items: [ { path: 'index.mdx', // optional: restrict the input files only: ['petstore.yaml'], // optional: set frontmatter description: 'All available pages', }, ], }, }); ``` ### Imports Add custom imports to generated MDX files. This is useful for including components, utilities, or other dependencies in your generated documentation. ```ts import { generateFiles } from 'fumadocs-openapi'; void generateFiles({ input: ['./petstore.yaml'], output: './content/docs', imports: [ { names: ['CustomComponent', 'AnotherComponent'], from: '@/components/custom', }, { names: ['utils'], from: '@/lib/utils', }, { names: ['API_BASE_URL'], from: '@/constants', }, ], }); ``` This will add the following imports to all generated MDX files: ```ts import { CustomComponent, AnotherComponent } from '@/components/custom'; import { utils } from '@/lib/utils'; import { API_BASE_URL } from '@/constants'; ``` ### Name A function that controls the output path of MDX pages. ```ts import { generateFiles } from 'fumadocs-openapi'; void generateFiles({ name: (output, document) => { if (output.type === 'operation') { const operation = document.paths![output.item.path]![output.item.method]!; // operation object console.log(operation); return 'my-dir/filename'; } const hook = document.webhooks![output.item.name][output.item.method]!; // webhook object console.log(hook); return 'my-dir/filename'; }, }); ``` ### Frontmatter Customise the frontmatter of MDX files. By default, it includes: | property | description | | ------------- | ------------------------------------------------ | | `title` | Page title | | `description` | Page description | | `full` | Always true, added for Fumadocs UI | | `method` | Available method of operation (`operation` mode) | | `route` | Route of operation (`operation` mode) | ```ts import { generateFiles } from 'fumadocs-openapi'; void generateFiles({ input: ['./petstore.yaml'], output: './content/docs', frontmatter: (title, description) => ({ myProperty: 'hello', }), }); ``` ### Add Generated Comment Add a comment to the top of generated files indicating they are auto-generated. ```ts import { generateFiles } from 'fumadocs-openapi'; void generateFiles({ input: ['./petstore.yaml'], output: './content/docs', // Add default comment addGeneratedComment: true, // Or provide a custom comment addGeneratedComment: 'Custom auto-generated comment', // Or disable comments addGeneratedComment: false, }); ``` ### Tag Display Name Adding `x-displayName` to OpenAPI Schema can control the display name of your tags. ```yaml title="openapi.yaml" tags: - name: test description: this is a tag. x-displayName: My Test Name ``` ## OpenAPI Server The server to render pages. ### Input * File Paths * External URLs * A function (see below) Basic Function ```ts import { createOpenAPI } from 'fumadocs-openapi/server'; export const openapi = createOpenAPI({ input: ['./unkey.json'], }); ``` ```ts import { createOpenAPI } from 'fumadocs-openapi/server'; export const openapi = createOpenAPI({ async input() { return { // [id]: downloaded OpenAPI Schema my_schema: await fetch( 'https://registry.scalar.com/@scalar/apis/galaxy/latest?format=json', ).then((res) => res.json()), }; }, }); ``` ### Generate Code Samples Generate custom code samples for each API endpoint. Make sure to install the types package to give you type-safety when customising it: npm pnpm yarn bun ```bash npm install openapi-types -D ``` ```bash pnpm add openapi-types -D ``` ```bash yarn add openapi-types --dev ``` ```bash bun add openapi-types --dev ``` ```ts import { createOpenAPI } from 'fumadocs-openapi/server'; export const openapi = createOpenAPI({ generateCodeSamples(endpoint) { return [ { lang: 'js', label: 'JavaScript SDK', source: "console.log('hello')", }, ]; }, }); ``` In addition, you can also specify code samples via OpenAPI schema. ```yaml paths: /plants: get: x-codeSamples: - lang: js label: JavaScript SDK source: | const planter = require('planter'); planter.list({ unwatered: true }); ``` #### Disable Code Sample You can disable the code sample for a specific language, for example, to disable cURL: ```ts import { createOpenAPI } from 'fumadocs-openapi/server'; export const openapi = createOpenAPI({ generateCodeSamples(endpoint) { return [ { lang: 'curl', label: 'cURL', source: false, }, ]; }, }); ``` ### Renderer Customise components in the page. ```ts import { createOpenAPI } from 'fumadocs-openapi/server'; export const openapi = createOpenAPI({ renderer: { Root(props) { // your own (server) component }, }, }); ``` # Fumadocs Framework: OpenAPI URL: /docs/ui/openapi Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/openapi/index.mdx Generating docs for OpenAPI schema It only works under RSC environments. ## Manual Setup Install the required packages. npm pnpm yarn bun ```bash npm i fumadocs-openapi shiki ``` ```bash pnpm add fumadocs-openapi shiki ``` ```bash yarn add fumadocs-openapi shiki ``` ```bash bun add fumadocs-openapi shiki ``` If you encountered any issues, please check [#2223](https://github.com/fuma-nama/fumadocs/issues/2223). ### Generate Styles Add the following line: ```css title="Tailwind CSS" @import 'tailwindcss'; @import 'fumadocs-ui/css/neutral.css'; @import 'fumadocs-ui/css/preset.css'; /* [!code ++] */ @import 'fumadocs-openapi/css/preset.css'; ``` ### Configure Pages Create an OpenAPI instance on the server. ```ts title="lib/openapi.ts" import { createOpenAPI } from 'fumadocs-openapi/server'; export const openapi = createOpenAPI({ // the OpenAPI schema, you can also give it an external URL. input: ['./unkey.json'], }); ``` ```ts title="lib/source.ts" import { openapiPlugin } from 'fumadocs-openapi/server'; import { loader } from 'fumadocs-core/source'; export const source = loader({ // [!code ++] adds a badge to each page item in page tree plugins: [openapiPlugin()], }); ``` Add `APIPage` to your MDX Components, so that you can use it in MDX files. ```tsx title="mdx-components.tsx" import defaultComponents from 'fumadocs-ui/mdx'; import { APIPage } from 'fumadocs-openapi/ui'; import { openapi } from '@/lib/openapi'; import type { MDXComponents } from 'mdx/types'; export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultComponents, APIPage: (props) => , ...components, }; } ``` > `APIPage` is a React Server Component. ### Generate Files You can generate MDX files directly from your OpenAPI schema. Create a script: ```js title="scripts/generate-docs.ts" 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, }); ``` > Only OpenAPI 3.0 and 3.1 are supported. Generate docs with the script: ```bash bun ./scripts/generate-docs.ts ``` ## Features 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 [View demo](/docs/openapi). # Fumadocs Framework: Media Adapters URL: /docs/ui/openapi/media-adapters Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/openapi/media-adapters.mdx Support other media types ## Overview A media adapter in Fumadocs supports: * Converting value into `fetch()` body compatible with corresponding media type. * Generate code example based on different programming language/tool. Put your media adapters in a separate file. lib/media-adapters.ts lib/media-adapters.client.ts ```ts twoslash import type { MediaAdapter } from 'fumadocs-openapi'; export const myAdapter: MediaAdapter = { encode(data) { return JSON.stringify(data.body); }, // returns code that inits a `body` variable, used for request body generateExample(data, ctx) { if (ctx.lang === 'js') { return `const body = "hello world"`; } if (ctx.lang === 'python') { return `body = "hello world"`; } if (ctx.lang === 'go' && 'addImport' in ctx) { ctx.addImport('strings'); return `body := strings.NewReader("hello world")`; } }, }; ``` ```ts 'use client'; // forward them so that Fumadocs can also use your media adapter in a client component export { myAdapter } from './media-adapters'; ``` Pass the adapter. ```ts title="lib/source.ts" import { createOpenAPI } from 'fumadocs-openapi/server'; import * as Adapters from './media-adapters'; import * as ClientAdapters from './media-adapters.client'; export const openapi = createOpenAPI({ proxyUrl: '/api/proxy', mediaAdapters: { // [!code ++:4] override the default adapter of `application/json` 'application/json': { ...Adapters.myAdapter, client: ClientAdapters.myAdapter, }, }, }); ``` # Fumadocs Framework: Creating Proxy URL: /docs/ui/openapi/proxy Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/openapi/proxy.mdx Avoid CORS problem ## Introduction A proxy server is useful for executing HTTP (`fetch`) requests, as it doesn't have CORS constraints like on the browser. We can use it for executing HTTP requests on the OpenAPI playground, when the target API endpoints do not have CORS configured correctly. Do not use this on unreliable sites and API endpoints, the proxy server will forward all received headers & body, including HTTP-only `Cookies` and `Authorization` header. ### Setup Create a route handler for proxy server. ```ts title="/api/proxy/route.ts" import { openapi } from '@/lib/openapi'; export const { GET, HEAD, PUT, POST, PATCH, DELETE } = openapi.createProxy({ // optional, we recommend to set a list of allowed origins for proxied requests allowedOrigins: ['https://example.com'], }); ``` > Follow the [Getting Started](/docs/ui/openapi) guide if `openapi` server is not yet configured. And enable the proxy from `createOpenAPI`. ```ts title="lib/source.ts" import { createOpenAPI } from 'fumadocs-openapi/server'; export const openapi = createOpenAPI({ proxyUrl: '/api/proxy', // [!code ++] }); ``` # Fumadocs Framework: Algolia URL: /docs/ui/search/algolia Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/search/algolia.mdx Using Algolia with Fumadocs UI. ## Overview For the setup guide, see [Integrate Algolia Search](/docs/headless/search/algolia). While generally we recommend building your own search with their client-side SDK, you can also plug the built-in dialog interface. ## Setup Create a search dialog, replace `appId`, `apiKey` and `indexName` with your desired values. ```tsx title="components/search.tsx" 'use client'; import { liteClient } from 'algoliasearch/lite'; import { SearchDialog, SearchDialogClose, SearchDialogContent, SearchDialogFooter, SearchDialogHeader, SearchDialogIcon, SearchDialogInput, SearchDialogList, SearchDialogOverlay, type SharedProps, } from 'fumadocs-ui/components/dialog/search'; import { useDocsSearch } from 'fumadocs-core/search/client'; import { useI18n } from 'fumadocs-ui/contexts/i18n'; const appId = 'replace me'; const apiKey = 'replace me'; const client = liteClient(appId, apiKey); export default function CustomSearchDialog(props: SharedProps) { const { locale } = useI18n(); // (optional) for i18n const { search, setSearch, query } = useDocsSearch({ type: 'algolia', client, indexName: 'document', locale, }); return (
Search powered by Algolia ); } ``` `useDocsSearch()` doesn't use instant search (their official Javascript client). ### Replace Search Dialog Replace the search dialog with yours from [``](/docs/ui/root-provider): ```tsx import { RootProvider } from 'fumadocs-ui/provider/'; // [!code ++] import SearchDialog from '@/components/search'; {children} ; ``` If it was in a server component, you would need a separate client component for provider to pass functions: provider.tsx app/layout.tsx ```tsx 'use client'; import { RootProvider } from 'fumadocs-ui/provider/'; import SearchDialog from '@/components/search'; import type { ReactNode } from 'react'; export function Provider({ children }: { children: ReactNode }) { return ( {children} ); } ``` ```tsx import { Provider } from './provider'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return ( {/* [!code --] */} {children} {/* [!code ++] */} {children} ); } ``` ### Tag Filter Optionally, you can add UI for filtering results by tags. Configure [Tag Filter](/docs/headless/search/algolia#tag-filter) on search server and add the following: ```tsx 'use client'; import { SearchDialog, SearchDialogContent, SearchDialogFooter, SearchDialogOverlay, type SharedProps, TagsList, TagsListItem, } from 'fumadocs-ui/components/dialog/search'; import { useState } from 'react'; import { useDocsSearch } from 'fumadocs-core/search/client'; export default function CustomSearchDialog(props: SharedProps) { // [!code ++] const [tag, setTag] = useState(); const { search, setSearch, query } = useDocsSearch({ tag, // [!code ++] // other options depending on your search engine }); return ( ... {/* [!code ++:3] */} My Value ); } ``` # Fumadocs Framework: Search URL: /docs/ui/search Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/search/index.mdx Implement document search in your docs ## Search UI You can customise some configurations from root provider. For example, to disable search UI: ```tsx title="app/layout.tsx" import { RootProvider } from 'fumadocs-ui/provider/'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return ( {children} ); } ``` ### Hot Keys Customise the hot keys to trigger search dialog, by default it's K or Ctrl K. ```tsx import { RootProvider } from 'fumadocs-ui/provider/'; {children} ; ``` ## Search Client You can choose & configure the search client according to your search engine, it defaults to Orama search. ### Community Integrations A list of integrations maintained by community. * [Trieve Search](/docs/headless/search/trieve) ### Highlight Matches Search integrations can provide `contentWithHighlights` to highlight matches. After configuring search client UI according to the guides above, you can customise how it is rendered. ```tsx title="components/search.tsx" ( { // ... }} /> )} /> ``` # Fumadocs Framework: Mixedbread URL: /docs/ui/search/mixedbread Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/search/mixedbread.mdx Using Mixedbread with Fumadocs UI. ## Overview For the setup guide, see [Integrate Mixedbread Search](/docs/headless/search/mixedbread). ## Setup Create a search dialog, replace `apiKey` and `vectorStoreId` with your desired values. ```tsx title="components/search.tsx" 'use client'; import { SearchDialog, SearchDialogClose, SearchDialogContent, SearchDialogFooter, SearchDialogHeader, SearchDialogIcon, SearchDialogInput, SearchDialogList, SearchDialogOverlay, type SharedProps, } from 'fumadocs-ui/components/dialog/search'; import { useDocsSearch } from 'fumadocs-core/search/client'; import { useI18n } from 'fumadocs-ui/contexts/i18n'; import Mixedbread from '@mixedbread/sdk'; const client = new Mixedbread({ apiKey: 'mxb_xxxx', baseURL: 'https://api.example.com', // Optional, defaults to https://api.mixedbread.com }); export default function CustomSearchDialog(props: SharedProps) { const { locale } = useI18n(); // (optional) for i18n const { search, setSearch, query } = useDocsSearch({ type: 'mixedbread', client, vectorStoreId: 'vectorStoreId', locale, }); return ( Search powered by Mixedbread ); } ``` ### Replace Search Dialog Replace the search dialog with yours from [``](/docs/ui/root-provider): ```tsx import { RootProvider } from 'fumadocs-ui/provider/'; // [!code ++] import SearchDialog from '@/components/search'; {children} ; ``` If it was in a server component, you would need a separate client component for provider to pass functions: provider.tsx app/layout.tsx ```tsx 'use client'; import { RootProvider } from 'fumadocs-ui/provider/'; import SearchDialog from '@/components/search'; import type { ReactNode } from 'react'; export function Provider({ children }: { children: ReactNode }) { return ( {children} ); } ``` ```tsx import { Provider } from './provider'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return ( {/* [!code --] */} {children} {/* [!code ++] */} {children} ); } ``` ### Tag Filter Optionally, you can add UI for filtering results by tags. Configure [Tag Filter](/docs/headless/search/mixedbread#tag-filter) on search server and add the following: ```tsx 'use client'; import { SearchDialog, SearchDialogContent, SearchDialogFooter, SearchDialogOverlay, type SharedProps, TagsList, TagsListItem, } from 'fumadocs-ui/components/dialog/search'; import { useState } from 'react'; import { useDocsSearch } from 'fumadocs-core/search/client'; export default function CustomSearchDialog(props: SharedProps) { // [!code ++] const [tag, setTag] = useState(); const { search, setSearch, query } = useDocsSearch({ tag, // [!code ++] // other options depending on your search engine }); return ( ... {/* [!code ++:3] */} My Value ); } ``` # Fumadocs Framework: Orama Cloud URL: /docs/ui/search/orama-cloud Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/search/orama-cloud.mdx Using Orama Cloud with Fumadocs UI. ## Setup For the setup guide, see [Integrate Orama Cloud](/docs/headless/search/orama-cloud). Create a search dialog, replace `endpoint` and `api_key` with your desired values. ```tsx title="components/search.tsx" 'use client'; import { SearchDialog, SearchDialogClose, SearchDialogContent, SearchDialogFooter, SearchDialogHeader, SearchDialogIcon, SearchDialogInput, SearchDialogList, SearchDialogOverlay, type SharedProps, } from 'fumadocs-ui/components/dialog/search'; import { useDocsSearch } from 'fumadocs-core/search/client'; import { OramaCloud } from '@orama/core'; import { useI18n } from 'fumadocs-ui/contexts/i18n'; const client = new OramaCloud({ projectId: '', apiKey: '', }); export default function CustomSearchDialog(props: SharedProps) { const { locale } = useI18n(); // (optional) for i18n const { search, setSearch, query } = useDocsSearch({ type: 'orama-cloud', client, locale, }); return ( Search powered by Orama ); } ``` ### Replace Search Dialog Replace the search dialog with yours from [``](/docs/ui/root-provider): ```tsx import { RootProvider } from 'fumadocs-ui/provider/'; // [!code ++] import SearchDialog from '@/components/search'; {children} ; ``` If it was in a server component, you would need a separate client component for provider to pass functions: provider.tsx app/layout.tsx ```tsx 'use client'; import { RootProvider } from 'fumadocs-ui/provider/'; import SearchDialog from '@/components/search'; import type { ReactNode } from 'react'; export function Provider({ children }: { children: ReactNode }) { return ( {children} ); } ``` ```tsx import { Provider } from './provider'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return ( {/* [!code --] */} {children} {/* [!code ++] */} {children} ); } ``` # Fumadocs Framework: Orama (default) URL: /docs/ui/search/orama Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/search/orama.mdx The default search engine powered by Orama. ## Overview Fumadocs configures [Orama search engine](/docs/headless/search/orama) out-of-the-box. It works through a API endpoint (running on server), or a statically cached JSON file for static websites. ## Setup Create a search dialog. The UI has been configured by default, you can also re-create it for further customisations: ```tsx title="components/search.tsx" 'use client'; import { useDocsSearch } from 'fumadocs-core/search/client'; import { SearchDialog, SearchDialogClose, SearchDialogContent, SearchDialogHeader, SearchDialogIcon, SearchDialogInput, SearchDialogList, SearchDialogOverlay, type SharedProps, } from 'fumadocs-ui/components/dialog/search'; import { useI18n } from 'fumadocs-ui/contexts/i18n'; export default function DefaultSearchDialog(props: SharedProps) { const { locale } = useI18n(); // (optional) for i18n const { search, setSearch, query } = useDocsSearch({ type: 'fetch', locale, }); return ( ); } ``` For Static Export, you can configure [static mode](/docs/headless/search/orama#static-export) on search server, and use the `static` client: npm pnpm yarn bun ```bash npm install @orama/orama ``` ```bash pnpm add @orama/orama ``` ```bash yarn add @orama/orama ``` ```bash bun add @orama/orama ``` ```tsx title="components/search.tsx" 'use client'; import { SearchDialog, SearchDialogClose, SearchDialogContent, SearchDialogHeader, SearchDialogIcon, SearchDialogInput, SearchDialogList, SearchDialogOverlay, type SharedProps, } from 'fumadocs-ui/components/dialog/search'; import { useDocsSearch } from 'fumadocs-core/search/client'; import { create } from '@orama/orama'; import { useI18n } from 'fumadocs-ui/contexts/i18n'; function initOrama() { return create({ schema: { _: 'string' }, // https://docs.orama.com/docs/orama-js/supported-languages language: 'english', }); } export default function DefaultSearchDialog(props: SharedProps) { const { locale } = useI18n(); // (optional) for i18n const { search, setSearch, query } = useDocsSearch({ type: 'static', initOrama, locale, }); return ( ); } ``` ### Replace Search Dialog Replace the search dialog with yours from [``](/docs/ui/root-provider): ```tsx import { RootProvider } from 'fumadocs-ui/provider/'; // [!code ++] import SearchDialog from '@/components/search'; {children} ; ``` If it was in a server component, you would need a separate client component for provider to pass functions: provider.tsx app/layout.tsx ```tsx 'use client'; import { RootProvider } from 'fumadocs-ui/provider/'; import SearchDialog from '@/components/search'; import type { ReactNode } from 'react'; export function Provider({ children }: { children: ReactNode }) { return ( {children} ); } ``` ```tsx import { Provider } from './provider'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return ( {/* [!code --] */} {children} {/* [!code ++] */} {children} ); } ``` ### Tag Filter Optionally, you can add UI for filtering results by tags. Configure [Tag Filter](/docs/headless/search/orama#tag-filter) on search server and add the following: ```tsx 'use client'; import { SearchDialog, SearchDialogContent, SearchDialogFooter, SearchDialogOverlay, type SharedProps, TagsList, TagsListItem, } from 'fumadocs-ui/components/dialog/search'; import { useState } from 'react'; import { useDocsSearch } from 'fumadocs-core/search/client'; export default function CustomSearchDialog(props: SharedProps) { // [!code ++] const [tag, setTag] = useState(); const { search, setSearch, query } = useDocsSearch({ tag, // [!code ++] // other options depending on your search engine }); return ( ... {/* [!code ++:3] */} My Value ); } ``` # Fumadocs MDX (the built-in content source): Bun URL: /docs/mdx/loader/bun Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/(integrations)/loader/bun.mdx Access content in Bun. ## Setup Register the plugin in preload script: bunfig.toml scripts/preload.ts ```toml preload = ["./scripts/preload.ts"] ``` ```ts import { createMdxPlugin } from 'fumadocs-mdx/bun'; Bun.plugin(createMdxPlugin()); ``` Now running any script with Bun will automatically support accessing your content files. # Fumadocs MDX (the built-in content source): Loader URL: /docs/mdx/loader Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/(integrations)/loader/index.mdx Fumadocs MDX loader integration. ## 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. # Fumadocs MDX (the built-in content source): Node.js URL: /docs/mdx/loader/node Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/(integrations)/loader/node.mdx Access content in Node.js runtime. ## Setup Make sure to run the script under ESM environment. ```js title="scripts/example.js" import { register } from 'node:module'; register('fumadocs-mdx/node/loader', import.meta.url); // accessing content const { source } = await import('./lib/source'); console.log(source.getPages()); ``` # Fumadocs MDX (the built-in content source): React Router URL: /docs/mdx/vite/react-router Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/(integrations)/vite/react-router.mdx Use Fumadocs MDX with React Router ## Setup
### Installation npm pnpm yarn bun ```bash npm i fumadocs-mdx fumadocs-core @types/mdx ``` ```bash pnpm add fumadocs-mdx fumadocs-core @types/mdx ``` ```bash yarn add fumadocs-mdx fumadocs-core @types/mdx ``` ```bash bun add fumadocs-mdx fumadocs-core @types/mdx ``` Create the configuration file: ```ts title="source.config.ts" import { defineConfig, defineDocs } from 'fumadocs-mdx/config'; export const docs = defineDocs({ dir: 'content/docs', }); export default defineConfig(); ``` Add the Vite plugin: ```ts title="vite.config.ts" import { defineConfig } from 'vite'; import mdx from 'fumadocs-mdx/vite'; import * as MdxConfig from './source.config'; export default defineConfig({ plugins: [ // [!code ++] mdx(MdxConfig), // ... ], }); ``` Setup an import alias (optional): ```json title="tsconfig.json" { "compilerOptions": { "paths": { // [!code ++] "@/.source": [".source"] } } } ``` ### Integrate with Fumadocs To integrate with Fumadocs, make a `lib/source.ts` file: ```ts title="app/lib/source.ts" import { loader } from 'fumadocs-core/source'; import { create, docs } from '@/.source'; export const source = loader({ source: await create.sourceAsync(docs.doc, docs.meta), baseUrl: '/docs', }); ``` The `.source` folder will be generated when you run development server or production build. ### Done You can now write content in `content/docs` folder.
## Examples ### Rendering Content As React Router doesn't support RSC at the moment, use `toClientRenderer()` to lazy load MDX content as a component on browser. For example: ```tsx import type { Route } from './+types/page'; import { source } from '@/lib/source'; import { docs } from '@/.source'; import { toClientRenderer } from 'fumadocs-mdx/runtime/vite'; export async function loader({ params }: Route.LoaderArgs) { const slugs = params['*'].split('/').filter((v) => v.length > 0); const page = source.getPage(slugs); if (!page) throw new Response('Not found', { status: 404 }); return { path: page.path, }; } const renderer = toClientRenderer(docs.doc, ({ default: Mdx, frontmatter }) => { return (

{frontmatter.title}

); }); export default function Page(props: Route.ComponentProps) { const { path } = props.loaderData; const Content = renderer[path]; return ; } ``` # Fumadocs MDX (the built-in content source): Tanstack Start URL: /docs/mdx/vite/tanstack Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/(integrations)/vite/tanstack.mdx Use Fumadocs MDX with Tanstack Start & Router ## Setup
### Installation npm pnpm yarn bun ```bash npm i fumadocs-mdx fumadocs-core @types/mdx ``` ```bash pnpm add fumadocs-mdx fumadocs-core @types/mdx ``` ```bash yarn add fumadocs-mdx fumadocs-core @types/mdx ``` ```bash bun add fumadocs-mdx fumadocs-core @types/mdx ``` Create the configuration file: ```ts title="source.config.ts" import { defineConfig, defineDocs } from 'fumadocs-mdx/config'; export const docs = defineDocs({ dir: 'content/docs', }); export default defineConfig(); ``` Add the Vite plugin: ```ts title="vite.config.ts" import react from '@vitejs/plugin-react'; import { tanstackStart } from '@tanstack/react-start/plugin/vite'; import { defineConfig } from 'vite'; import tsConfigPaths from 'vite-tsconfig-paths'; import tailwindcss from '@tailwindcss/vite'; import mdx from 'fumadocs-mdx/vite'; export default defineConfig({ server: { port: 3000, }, plugins: [ mdx(await import('./source.config')), tailwindcss(), tsConfigPaths({ projects: ['./tsconfig.json'], }), tanstackStart({ prerender: { enabled: true, }, }), react(), ], }); ``` Setup an import alias (optional): ```json title="tsconfig.json" { "compilerOptions": { "paths": { // [!code ++] "@/.source": [".source"] } } } ``` ### Integrate with Fumadocs To integrate with Fumadocs, create a `lib/source.ts` file: ```ts title="src/lib/source.ts" import { loader } from 'fumadocs-core/source'; import { create, docs } from '@/.source'; export const source = loader({ source: await create.sourceAsync(docs.doc, docs.meta), baseUrl: '/docs', }); ``` The `.source` folder will be generated when you run development server or production build. ### Done You can now write content in `content/docs` folder.
## Examples ### Rendering Content As Tanstack Start doesn't support RSC at the moment, use `createClientLoader()` to lazy load MDX content as a component on browser. For example: ```tsx title="src/routes/docs/$.tsx" import { createFileRoute, notFound } from '@tanstack/react-router'; import { createServerFn } from '@tanstack/react-start'; import { source } from '@/lib/source'; import { docs } from '@/.source'; import { createClientLoader } from 'fumadocs-mdx/runtime/vite'; export const Route = createFileRoute('/docs/$')({ component: Page, loader: async ({ params }) => { const data = await loader({ data: params._splat?.split('/') ?? [] }); await clientLoader.preload(data.path); return data; }, }); const loader = createServerFn({ method: 'GET', }) .inputValidator((slugs: string[]) => slugs) .handler(async ({ data: slugs }) => { const page = source.getPage(slugs); if (!page) throw notFound(); return { path: page.path, }; }); const clientLoader = createClientLoader(docs.doc, { id: 'docs', component({ frontmatter, default: MDX }) { return (

{frontmatter.title}

); }, }); function Page() { const data = Route.useLoaderData(); const Content = clientLoader.getComponent(data.path); return ; } ``` # Fumadocs MDX (the built-in content source): Waku URL: /docs/mdx/vite/waku Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/(integrations)/vite/waku.mdx Use Fumadocs MDX with Waku ## Setup
### Installation npm pnpm yarn bun ```bash npm i fumadocs-mdx fumadocs-core fumadocs-ui @types/mdx ``` ```bash pnpm add fumadocs-mdx fumadocs-core fumadocs-ui @types/mdx ``` ```bash yarn add fumadocs-mdx fumadocs-core fumadocs-ui @types/mdx ``` ```bash bun add fumadocs-mdx fumadocs-core fumadocs-ui @types/mdx ``` Create the configuration file: ```ts title="source.config.ts" import { defineConfig, defineDocs } from 'fumadocs-mdx/config'; export const docs = defineDocs({ dir: 'content/docs', }); export default defineConfig(); ``` Add the Vite plugin: ```ts title="vite.config.ts" import { defineConfig } from 'waku/config'; import mdx from 'fumadocs-mdx/vite'; import * as MdxConfig from './source.config.js'; import tailwindcss from '@tailwindcss/vite'; import tsconfigPaths from 'vite-tsconfig-paths'; export default defineConfig({ vite: { plugins: [tailwindcss(), mdx(MdxConfig), tsconfigPaths()], }, }); ``` Setup an import alias (optional): ```json title="tsconfig.json" { "compilerOptions": { "paths": { // [!code ++] "@/.source": [".source"] } } } ``` ### Integrate with Fumadocs Create a `lib/source.ts` file: ```ts title="src/lib/source.ts" import { loader } from 'fumadocs-core/source'; import { create, docs } from '@/.source'; export const source = loader({ source: await create.sourceAsync(docs.doc, docs.meta), baseUrl: '/docs', }); ``` The `.source` folder will be generated when you run development server or production build. ### Done You can now write content in `content/docs` folder.
## Examples ### Rendering Pages Since Waku supports RSC, you can use the exported `default` component directly. ```tsx import { source } from '@/lib/source'; import type { PageProps } from 'waku/router'; import defaultMdxComponents from 'fumadocs-ui/mdx'; import { DocsBody, DocsDescription, DocsPage, DocsTitle, } from 'fumadocs-ui/page'; export default async function DocPage({ slugs, }: PageProps<'/docs/[...slugs]'>) { const page = source.getPage(slugs); if (!page) { // ... } const MDX = page.data.default; return ( {page.data.title} {page.data.description} ); } ``` # Fumadocs Framework: Docs Layout URL: /docs/ui/layouts/docs Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(ui)/layouts/docs.mdx The layout of documentation The layout of documentation pages, it includes a sidebar and mobile-only navbar. ## Usage Pass your page tree to the component. ```tsx title="layout.tsx" import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import { baseOptions } from '@/lib/layout.shared'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return ( {children} ); } ``` ## Sidebar ```tsx title="layout.tsx" import { DocsLayout } from 'fumadocs-ui/layouts/docs'; ; ``` > See [Sidebar Links](/docs/ui/navigation/sidebar) for customising sidebar items. ## Nav A mobile-only navbar, the `nav` prop is also reusable on other Fumadocs layouts.
Docs Nav
```tsx import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import { baseOptions } from '@/lib/layout.shared'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { const base = baseOptions(); return ( {children} ); } ``` ### Transparent Mode To make the navbar background transparent, you can configure transparent mode. ```tsx import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export const baseOptions: BaseLayoutProps = { nav: { transparentMode: 'top', }, }; ``` | Mode | Description | | -------- | ---------------------------------------- | | `always` | Always use a transparent background | | `top` | When at the top of page | | `none` | Disable transparent background (default) | ### Replace Navbar To replace the navbar in Docs Layout, set `nav.component` to your own component. ```tsx title="layout.tsx" import { baseOptions } from '@/lib/layout.shared'; import { DocsLayout } from 'fumadocs-ui/layouts/notebook'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return ( , }} > {children} ); } ``` Fumadocs uses **CSS Variables** to share the size of layout components, and fit each layout component into appropriate position. You need to override `--fd-nav-height` to the exact height of your custom navbar, this can be done with a CSS stylesheet (e.g. in `global.css`): ```css :root { --fd-nav-height: 80px !important; } ``` ## Advanced ### Disable Prefetching By default, it uses the `` component of your React framework with prefetch enabled. On Vercel, prefetch requests may cause a higher usage of serverless functions and Data Cache. It can also hit the limits of some other hosting platforms. You can disable prefetching to reduce the amount of prefetch requests. ```tsx import { DocsLayout } from 'fumadocs-ui/layouts/docs'; ; ``` # Fumadocs Framework: Home Layout URL: /docs/ui/layouts/home-layout Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(ui)/layouts/home-layout.mdx Shared layout for other pages ## Usage Add a navbar and search dialog across other pages. ```tsx title="/app/(home)/layout.tsx" 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 {children}; } ``` We recommend to customise it from [`baseOptions`](/docs/ui/navigation/links). # Fumadocs Framework: Notebook URL: /docs/ui/layouts/notebook Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(ui)/layouts/notebook.mdx A more compact version of Docs Layout Notebook ## Usage Enable the notebook layout with `fumadocs-ui/layouts/notebook`, it's a more compact layout than the default one. ```tsx title="layout.tsx" import { DocsLayout } from 'fumadocs-ui/layouts/notebook'; // [!code highlight] import { baseOptions } from '@/lib/layout.shared'; import { source } from '@/lib/source'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return ( {children} ); } ``` ## Configurations The options are inherited from [Docs Layout](/docs/ui/layouts/docs), with minor differences: * sidebar/navbar cannot be replaced, Notebook layout is more opinionated than the default one. * additional options (see below). ### Tab Mode Configure the style of sidebar tabs. Notebook ```tsx title="layout.tsx" import { DocsLayout } from 'fumadocs-ui/layouts/notebook'; import { baseOptions } from '@/lib/layout.shared'; import { source } from '@/lib/source'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return ( {children} ); } ``` ### Nav Mode Configure the style of navbar. Notebook ```tsx title="layout.tsx" import { DocsLayout } from 'fumadocs-ui/layouts/notebook'; import { baseOptions } from '@/lib/layout.shared'; import { source } from '@/lib/source'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { const { nav, ...base } = baseOptions(); return ( {children} ); } ``` # Fumadocs Framework: Docs Page URL: /docs/ui/layouts/page Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(ui)/layouts/page.mdx A page in your documentation Page is the base element of a documentation, it includes Table of contents, Footer, and Breadcrumb. ## Usage ```tsx title="page.tsx" import { DocsPage, DocsDescription, DocsTitle, DocsBody, } from 'fumadocs-ui/page'; title description

This heading looks good!

It applies the Typography styles, wrap your content here.
; ``` Instead of rendering the title with `DocsTitle` in `page.tsx`, you can put the title into MDX file. This will render the title in the MDX body. ### Edit on GitHub You can also add your own component. ```tsx import { DocsBody } from 'fumadocs-ui/page'; Edit on GitHub ; ``` ## Configurations ### Full Mode To extend the page to fill up all available space, pass `full` to the page component. This will force TOC to be shown as a popover. ```tsx import { DocsPage } from 'fumadocs-ui/page'; ...; ``` ### Table of Contents An overview of all the headings in your article, it requires an array of headings. For Markdown and MDX documents, You can obtain it using the [TOC Utility](/docs/headless/utils/get-toc). Content sources like Fumadocs MDX offer this out-of-the-box. ```tsx import { DocsPage } from 'fumadocs-ui/page'; ...; ``` You can customise or disable it with the `tableOfContent` option, or with `tableOfContentPopover` on smaller devices. ```tsx import { DocsPage } from 'fumadocs-ui/page'; ... ; ``` #### Style You can choose another style for TOC, like `clerk` inspired by [https://clerk.com](https://clerk.com): ```tsx import { DocsPage } from 'fumadocs-ui/page'; ... ; ``` You can also style the TOC title with the `toc-title` ID. ### Last Updated Time Display last updated time of the page. ```tsx import { DocsPage } from 'fumadocs-ui/page'; ; ``` Since you might use different version controls (e.g. Github) or CMS like Sanity, Fumadocs UI doesn't display the last updated time by default. You can enable [`lastModifiedTime`](/docs/mdx/last-modified). ```tsx import { DocsPage } from 'fumadocs-ui/page'; import { source } from '@/lib/source'; const page = source.getPage(['...']); ; ``` For Github hosted documents, you can use the [`getGithubLastEdit`](/docs/headless/utils/git-last-edit) utility. ```tsx import { DocsPage } from 'fumadocs-ui/page'; import { getGithubLastEdit } from 'fumadocs-core/content/github'; const time = await getGithubLastEdit({ owner: 'fuma-nama', repo: 'fumadocs', path: `content/docs/${page.path}`, }); ; ``` ### Footer Footer is a navigation element that has two buttons to jump to the next and previous pages. When not specified, it shows the neighbour pages found from page tree. Customise the footer with the `footer` option. ```tsx import { DocsPage, DocsBody } from 'fumadocs-ui/page'; ... ; ``` ### Breadcrumb A navigation element, shown only when user is navigating in folders. # Fumadocs Framework: Root Provider URL: /docs/ui/layouts/root-provider Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(ui)/layouts/root-provider.mdx The context provider of Fumadocs UI. The context provider of all the components, including `next-themes` and context for search dialog. It should be located at the root layout. ## Usage Import it according to your React.js framework: Next.js React Router Tanstack Waku ```jsx import { RootProvider } from 'fumadocs-ui/provider/next'; export default function Layout({ children }) { return ( {children} ); } ``` ```jsx import { RootProvider } from 'fumadocs-ui/provider/react-router'; export function Layout({ children }) { return ( {children} ); } ``` ```jsx import { HeadContent, Scripts, } from '@tanstack/react-router'; import { RootProvider } from 'fumadocs-ui/provider/tanstack'; function RootDocument({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ```jsx import { RootProvider } from 'fumadocs-ui/provider/waku'; export default function Layout({ children }) { return ( {children} ); } ``` ### Search Dialog Customize or disable the search dialog with `search` option. ```jsx {children} ``` Learn more from [Search](/docs/ui/search). ### Theme Provider Fumadocs supports light/dark modes with [`next-themes`](https://github.com/pacocoursey/next-themes). Customise or disable it with `theme` option. ```jsx {children} ``` # Fumadocs Framework: Accordion URL: /docs/ui/components/accordion Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(ui)/components/accordion.mdx Add Accordions to your documentation ## Usage Based on [Radix UI Accordion](https://www.radix-ui.com/primitives/docs/components/accordion), useful for FAQ sections. Use it in MDX files or as a normal React component. MDX React.js ```mdx --- title: Hello World --- import { Accordion, Accordions } from 'fumadocs-ui/components/accordion'; My Content ``` ```tsx import { Accordion, Accordions } from 'fumadocs-ui/components/accordion'; export default function Page() { return ( My Content ); } ``` ### Accordions ### Accordion ### Linking to Accordion You can specify an `id` for accordion. The accordion will automatically open when the user is navigating to the page with the specified `id` in hash parameter. ```mdx My Content ``` > The value of accordion is same as title by default. When an id presents, it will be used as the value instead. # Fumadocs Framework: Auto Type Table URL: /docs/ui/components/auto-type-table Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(ui)/components/auto-type-table.mdx Auto-generated type table string /** * We love Shiki. * * \`\`\`ts * console.log("Hello World, powered by Shiki"); * \`\`\` * @default { a: "test" } */ options?: Partial<{ a: unknown }>; }`} /> You cannot use this in a client component. It generates a table for your docs based on TypeScript definitions. ## Usage npm pnpm yarn bun ```bash npm i fumadocs-typescript ``` ```bash pnpm add fumadocs-typescript ``` ```bash yarn add fumadocs-typescript ``` ```bash bun add fumadocs-typescript ``` Initialize the TypeScript compiler and add it as a MDX component. ```tsx title="mdx-components.tsx" import defaultComponents from 'fumadocs-ui/mdx'; import type { MDXComponents } from 'mdx/types'; import { createGenerator } from 'fumadocs-typescript'; import { AutoTypeTable } from 'fumadocs-typescript/ui'; const generator = createGenerator(); export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultComponents, // [!code ++:3] AutoTypeTable: (props) => ( ), ...components, }; } ``` You can now reference `` in your MDX content. ### From File It accepts a `path` prop that points to a typescript file, and `name` for the exported type name. ```ts title="path/to/file.ts" export interface MyInterface { name: string; } ``` ```mdx title="content.mdx" ``` The path is relative to your project directory (`cwd`), because `AutoTypeTable` is a React Server Component, it cannot access build-time information like MDX file path. ### From Type You can specify the type to generate, without an actual TypeScript file. ```mdx title="content.mdx" ``` When a `path` is given, it shares the same context as the TypeScript file. ```ts title="file.ts" export type A = { hello: string }; ``` ```mdx title="content.mdx" ``` When `type` has multiple lines, the export statement and `name` prop are required. ```mdx title="content.mdx" ``` ### Functions Notice that only object type is allowed. For functions, you should wrap them into an object instead. ```ts export interface MyInterface { myFn: (input: string) => void; } ``` ## TypeScript Compiler Under the hood, it uses the [Typescript Compiler API](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API) to extract type information. Your `tsconfig.json` file in the current working directory will be loaded. You can change the compiler settings from [`createGenerator()`](/docs/ui/typescript). ```ts import { createGenerator } from 'fumadocs-typescript'; const generator = createGenerator({ tsconfigPath: './tsconfig.json', // where to resolve relative paths (normally cwd) basePath: './', // disable file system cache cache: false, }); ``` ### File System It relies on the file system, hence, the page referencing this component must be built in **build time**. Rendering the component on serverless runtime may cause problems. ## References \n```", "tags": [], "type": "string | undefined", "simplifiedType": "string", "required": false, "deprecated": false }, { "name": "generator", "description": "", "tags": [], "type": "{ generateDocumentation(file: { path: string; content?: string; }, name: string | undefined, options?: GenerateOptions): GeneratedDoc[]; generateTypeTable(props: BaseTypeTableProps, options?: GenerateTypeTableOptions): Promise; }", "simplifiedType": "object", "required": true, "deprecated": false }, { "name": "renderMarkdown", "description": "", "tags": [], "type": "((md: string) => Promise) | undefined", "simplifiedType": "function", "required": false, "deprecated": false }, { "name": "renderType", "description": "", "tags": [], "type": "((type: string) => Promise) | undefined", "simplifiedType": "function", "required": false, "deprecated": false }, { "name": "options", "description": "", "tags": [], "type": "GenerateTypeTableOptions | undefined", "simplifiedType": "object", "required": false, "deprecated": false } ] }} /> # Fumadocs Framework: Banner URL: /docs/ui/components/banner Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(ui)/components/banner.mdx Add a banner to your site ## Usage Put the element at the top of your root layout, you can use it for displaying announcements. ```tsx import { Banner } from 'fumadocs-ui/components/banner'; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( {/* [!code ++] */} Hello World {children} ); } ``` ### Variant Change the default variant. ```tsx import { Banner } from 'fumadocs-ui/components/banner'; Hello World; // customise colors Hello World ; ``` ### Change Layout By default, the banner uses a `style` tag to modify Fumadocs layouts (e.g. reduce the sidebar height). You can disable it with: ```tsx import { Banner } from 'fumadocs-ui/components/banner'; Hello World; ``` ### Close To allow users to close the banner, give the banner an ID. ```tsx import { Banner } from 'fumadocs-ui/components/banner'; Hello World; ``` The state will be automatically persisted. # Fumadocs Framework: Code Block URL: /docs/ui/components/codeblock Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(ui)/components/codeblock.mdx Displaying Shiki highlighted code blocks
```js title="config.js" import createMDX from 'fumadocs-mdx/config'; const withMDX = createMDX(); // [!code word:config] /** @type {import('next').NextConfig} */ const config = { // [!code highlight] reactStrictMode: true, // [!code highlight] }; // [!code highlight] export default withMDX(config); ```
This is a **MDX component** meant to be used with [Rehype Code](/docs/headless/mdx/rehype-code) to display highlighted codeblocks. You can refer to [Markdown](/docs/ui/markdown#codeblock) for the syntax of writing codeblocks. Supported features: * Copy button * Custom titles and icons > If you're looking for an equivalent with runtime syntax highlighting, see [Dynamic Code Block](/docs/ui/components/dynamic-codeblock). ## Usage Wrap the pre element in ``, which acts as the wrapper of code block. ```tsx title="mdx-components.tsx" import defaultComponents from 'fumadocs-ui/mdx'; import type { MDXComponents } from 'mdx/types'; import { CodeBlock, Pre } from 'fumadocs-ui/components/codeblock'; export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultComponents, // HTML `ref` attribute conflicts with `forwardRef` pre: ({ ref: _ref, ...props }) => (
{props.children}
{/* [!code highlight] */}
), ...components, }; } ``` ### Keep Background Use the background color generated by Shiki. ```tsx import { Pre, CodeBlock } from 'fumadocs-ui/components/codeblock';
{props.children}
; ``` ### Icons Specify a custom icon by passing an `icon` prop to `CodeBlock` component. By default, the icon will be injected by the custom Shiki transformer. ```js title="config.js" console.log('js'); ``` # Fumadocs Framework: Code Block (Dynamic) URL: /docs/ui/components/dynamic-codeblock Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(ui)/components/dynamic-codeblock.mdx A codeblock that also highlights code ## Usage ### Client Component ```tsx import { DynamicCodeBlock } from 'fumadocs-ui/components/dynamic-codeblock'; export function MyComp() { const code = `console.log("Hello World")`; return ; } ``` Unlike the MDX [`CodeBlock`](/docs/ui/components/codeblock) component, this is a client component that can be used without MDX. It highlights the code with Shiki and use the default component to render it. Features: * using React.js 19 Suspense. * can be pre-rendered on server. * load languages and themes on browser lazily. #### Options ```tsx import { DynamicCodeBlock } from 'fumadocs-ui/components/dynamic-codeblock'; ; ``` ### Server Component For a server component equivalent, you can use the built-in utility from core: ```tsx import * as Base from 'fumadocs-ui/components/codeblock'; import { highlight } from 'fumadocs-core/highlight'; import { type HTMLAttributes } from 'react'; export async function CodeBlock({ code, lang, ...rest }: HTMLAttributes & { code: string; lang: string; }) { const rendered = await highlight(code, { lang, components: { pre: (props) => , }, // other Shiki options }); return {rendered}; } ``` # Fumadocs Framework: Files URL: /docs/ui/components/files Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(ui)/components/files.mdx Display file structure in your documentation ## Usage Wrap file components in `Files`, you can use it in your MDX content, or as a normal React.js component. ```mdx title="content.mdx" import { File, Folder, Files } from 'fumadocs-ui/components/files'; ``` ### As CodeBlock You can enable [`remark-mdx-files`](/docs/headless/mdx/remark-mdx-files) to define file structure with codeblocks. ```tsx title="source.config.ts (Fumadocs MDX)" import { remarkMdxFiles } from 'fumadocs-core/mdx-plugins'; import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { // [!code ++] remarkPlugins: [remarkMdxFiles], }, }); ``` it will convert `files` codeblocks into MDX usage, like: ````md title="content.md" ```files project ├── src │ ├── index.js │ └── utils │ └── helper.js ├── package.json ``` ```` ### File ### Folder # Fumadocs Framework: GitHub Info URL: /docs/ui/components/github-info Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(ui)/components/github-info.mdx Display your GitHub repository information ## Usage ```tsx import { GithubInfo } from 'fumadocs-ui/components/github-info'; export function MyComp() { return ( ); } ``` It's recommended to add it to your docs layout with `links` option: ```tsx title="app/docs/layout.tsx" import { DocsLayout, type DocsLayoutProps } from 'fumadocs-ui/layouts/notebook'; import { baseOptions } from '@/lib/layout.shared'; import { source } from '@/lib/source'; import { GithubInfo } from 'fumadocs-ui/components/github-info'; function docsOptions(): DocsLayoutProps { return { ...baseOptions(), tree: source.pageTree, links: [ { type: 'custom', children: ( ), }, ], }; } export default function Layout({ children }: { children: React.ReactNode }) { return {children}; } ``` # Fumadocs Framework: Graph View URL: /docs/ui/components/graph-view Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(ui)/components/graph-view.mdx A graph of all pages. import { GraphView } from '@/components/preview/lazy'; import { buildGraph } from '@/lib/build-graph'; ## Installation You can install this from CLI: ## Usage You can use it in MDX files or the layout components (e.g. in `page.tsx`): ```tsx title="page.tsx" import { GraphView } from '@/components/graph-view'; import { buildGraph } from '@/lib/build-graph'; export function PageBody() { return (
{/* ... */}
); } ``` # Fumadocs Framework: Zoomable Image URL: /docs/ui/components/image-zoom Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(ui)/components/image-zoom.mdx Allow zoom-in images in your documentation ## Usage Replace `img` with `ImageZoom` in your MDX components. ```tsx title="mdx-components.tsx" import { ImageZoom } from 'fumadocs-ui/components/image-zoom'; import defaultComponents from 'fumadocs-ui/mdx'; import type { MDXComponents } from 'mdx/types'; export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultComponents, // [!code ++] img: (props) => , ...components, }; } ``` Now image zoom will be automatically enabled on all images. ```mdx ![Test](/banner.png) ``` ### Image Optimization On Next.js, a default [`sizes` property](https://nextjs.org/docs/app/api-reference/components/image#sizes) will be defined for `` component if not specified. # Fumadocs Framework: Components URL: /docs/ui/components Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(ui)/components/index.mdx Additional components to improve your docs ## Overview Additional components that you can use: ### MDX Components The default MDX components include Cards, Callouts, Code Blocks and Headings. ```ts import defaultMdxComponents from 'fumadocs-ui/mdx'; ``` ### Relative Link Server Component only. To support links with relative file path in `href`, override the default `a` component with: ```tsx title="app/docs/[[...slug]]/page.tsx" import { createRelativeLink } from 'fumadocs-ui/mdx'; import { source } from '@/lib/source'; import { getMDXComponents } from '@/mdx-components'; const page = source.getPage(['...']); return ( ); ``` ```mdx [My Link](./file.mdx) ``` Example: [`../../(integrations)/feedback.mdx`](../../\(integrations\)/feedback.mdx) # Fumadocs Framework: Inline TOC URL: /docs/ui/components/inline-toc Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(ui)/components/inline-toc.mdx Add Inline TOC into your documentation ## Usage You can use it in your MDX content: ```mdx title="content.mdx" import { InlineTOC } from 'fumadocs-ui/components/inline-toc'; Table of Contents ``` Or adding it to every page. ```tsx title="page.tsx" import { DocsPage } from 'fumadocs-ui/page'; import { InlineTOC } from 'fumadocs-ui/components/inline-toc'; export default function Page() { // ... return ( {/* [!code ++] */} Table of Contents ); } ``` ## Reference # Fumadocs Framework: Steps URL: /docs/ui/components/steps Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(ui)/components/steps.mdx Adding steps to your docs ## Usage Put your steps into the `Steps` container. ```mdx title="content.mdx" import { Step, Steps } from 'fumadocs-ui/components/steps'; ### Hello World ### Hello World ``` ### Without imports You can use the Tailwind CSS utilities without importing it. ```mdx
``` It supports adding step styles to only headings with arbitrary variants. ```mdx
### Hello World
```
### Hello World You no longer need to use the step component anymore.
# Fumadocs Framework: Tabs URL: /docs/ui/components/tabs Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(ui)/components/tabs.mdx A Tabs component built with Radix UI, with additional features such as persistent and shared value. import { UrlBar } from './tabs.client'; ## Usage Add MDX components. ```tsx title="mdx-components.tsx" import defaultMdxComponents from 'fumadocs-ui/mdx'; import * as TabsComponents from 'fumadocs-ui/components/tabs'; import type { MDXComponents } from 'mdx/types'; export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultMdxComponents, ...TabsComponents, // [!code ++] ...components, }; } ``` And use it in your MDX content: ```mdx title="content.mdx" Javascript is weird Rust is fast ``` Javascript is weird Rust is fast ### Without `value` Without a `value`, it detects from the children index. Note that it might cause errors on re-renders, it's not encouraged if the tabs might change. ```mdx import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; Javascript is weird Rust is fast ``` Javascript is weird Rust is fast ### Shared Value By passing an `groupId` property, you can share a value across all tabs with the same id. ```mdx Javascript is weird Rust is fast ``` ### Persistent You can enable persistent by passing a `persist` property. The value will be stored in `localStorage`, with its id as the key. ```mdx Javascript is weird Rust is fast ``` > Persistent only works if you have passed an `id`. ### Default Value Set a default value by passing `defaultIndex`. ```mdx Javascript is weird Rust is fast ``` ### Link to Tab Use HTML `id` attribute to link to a specific tab. ```mdx Javascript is weird Rust is fast `Hello World` ``` You can add the hash `#tab-cpp` to your URL and reload, the C++ tab will be activated. Javascript is weird Rust is fast `Hello World` Additionally, the `updateAnchor` property can be set to `true` in the `Tabs` component to automatically update the URL hash whenever time a new tab is selected: ```mdx Javascript is weird Rust is fast `Hello World` ``` Hello! World! ## Advanced Usage Use it in the Radix UI primitive way, see [Radix UI](https://radix-ui.com/primitives/docs/components/tabs) for more details. ```mdx npm Hello World ``` npm npm Hello World # Fumadocs Framework: Type Table URL: /docs/ui/components/type-table Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(ui)/components/type-table.mdx A table for documenting types ## Usage It accepts a `type` property. ```mdx import { TypeTable } from 'fumadocs-ui/components/type-table'; ``` ## References ### Type Table ### Object Type