# blade47/fumadocs Wiki
## MDX Configuration Loading
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/mdx-configuration-loading
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/mdx/src/next/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/next/index.ts)
- [packages/mdx/src/config/load-from-file.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/config/load-from-file.ts)
- [packages/mdx/src/core.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/core.ts)
The `createMDX` process acts as the integration layer between Fumadocs and Next.js. At a high level, this flow initializes the MDX core, manages configuration loading, and prepares the necessary paths so that loaders can resolve the compiled MDX configuration during the build or development process.
This process is critical for ensuring that MDX files are transformed using the correct project-specific configuration. By dynamically determining the location of the compiled configuration file, the system maintains a seamless link between user-defined `source.config.ts` files and the underlying build tools.
### Step-by-Step Execution
#### 1. `createMDX`
The entry point `createMDX` is invoked by the user's `next.config.mjs`. It orchestrates the setup of the `Core` instance and checks if the environment needs initialization. If `_FUMADOCS_MDX` is not set, it triggers the `init` function to start the development environment or initial compilation.
Sources: [packages/mdx/src/next/index.ts:30-38](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/next/index.ts#L30-L38)
#### 2. `init`
The `init` function prepares the environment. It defines internal utilities for managing config lifecycle (initial load and reload) and handles the development server instance if the environment is in development mode.
Sources: [packages/mdx/src/next/index.ts:124-182](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/next/index.ts#L124-L182)
#### 3. `devServer`
When in development, `devServer` initializes a file watcher (via `chokidar`) to monitor changes to the configuration file or content collections. It ensures that whenever a user modifies the configuration, the system triggers a reload of the core logic.
Sources: [packages/mdx/src/next/index.ts:132-176](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/next/index.ts#L132-L176)
#### 4. `initOrReload`
This function is called both during startup and on file changes. It executes `core.init` and passes in the loaded configuration, which effectively re-hydrates the application with the latest user settings.
Sources: [packages/mdx/src/next/index.ts:125-130](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/next/index.ts#L125-L130)
#### 5. `loadConfig`
This utility compiles the source configuration file using `esbuild` if requested, and then imports the generated module. It ensures the configuration is ready to be consumed by the plugins and core modules.
Sources: [packages/mdx/src/config/load-from-file.ts:35-44](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/config/load-from-file.ts#L35-L44)
#### 6. `getCompiledConfigPath`
Finally, this method on the `Core` object computes the expected filesystem location of the `source.config.mjs` file. This path is then used by the Webpack loaders to access the compiled configuration variables during the compilation of MDX content.
Sources: [packages/mdx/src/core.ts:218-220](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/core.ts#L218-L220)
> [!NOTE]
> The `getCompiledConfigPath` method does not verify file existence; it merely provides the standard path based on the output directory configuration.
### Sequence Diagram
```mermaid
sequenceDiagram
participant Index as next/index.ts
participant Config as config/load-from-file.ts
participant Core as core.ts
Index->>Index: createMDX()
Index->>Index: init()
Index->>Index: initOrReload()
Index->>Config: loadConfig()
Config->>Core: getCompiledConfigPath()
Core-->>Config: return path
Config-->>Index: return LoadedConfig
```
Sources: [packages/mdx/src/next/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/next/index.ts)
### Flowchart
```mermaid
flowchart TD
A[createMDX] --> B[init]
B --> C[initOrReload]
C --> D[loadConfig]
D --> E[getCompiledConfigPath]
```
Sources: [packages/mdx/src/next/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/next/index.ts)
### Key Observations
* **Boundary Crossing:** The flow transitions from the `next` package integration layer to the `config` management utilities and finally to the `core` logic layer.
* **Failure Handling:** In `compileConfig`, failure to build the configuration file results in an explicit error, preventing further execution with invalid settings.
* **Performance:** The configuration loading process uses `Date.now()` as a search param on the import URL (in `loadConfig`) to bust the node module cache, ensuring that hot-reloaded configurations are always current.
* **Watcher Logic:** The `devServer` uses `chokidar` to detect configuration changes. It calls `watcher.removeAllListeners()` before restarting to prevent listener leaks during hot reloads.
> [!TIP]
> If you encounter issues where MDX configuration changes aren't being reflected, ensure that your `outDir` (default `.source`) is not being ignored by your IDE or build cache.
> [!WARNING]
> Manual editing of the files inside the `.source` directory is discouraged as they are generated by `esbuild` during the `loadConfig` process.
---
## Overview
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/overview
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/mdx/src/vite/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/vite/index.ts)
- [packages/preview/src/pages/[...slugs].tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/pages/%5B...slugs%5D.tsx)
- [packages/mdx/src/runtime/dynamic.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/dynamic.ts)
- [packages/content/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/content/src/index.ts)
- [packages/core/package.json](https://github.com/blade47/fumadocs/blob/main/packages/core/package.json)
- [packages/mdx/package.json](https://github.com/blade47/fumadocs/blob/main/packages/mdx/package.json)
- [packages/api-docs/package.json](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/package.json)
- [packages/local-md/package.json](https://github.com/blade47/fumadocs/blob/main/packages/local-md/package.json)
- [packages/openapi/package.json](https://github.com/blade47/fumadocs/blob/main/packages/openapi/package.json)
- [packages/content/src/runtime.ts](https://github.com/blade47/fumadocs/blob/main/packages/content/src/runtime.ts)
- [packages/preview/package.json](https://github.com/blade47/fumadocs/blob/main/packages/preview/package.json)
- [packages/base-ui/registry/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/registry/index.ts)
- [packages/typescript/package.json](https://github.com/blade47/fumadocs/blob/main/packages/typescript/package.json)
- [packages/mdx/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/tsdown.config.ts)
- [packages/obsidian/package.json](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/package.json)
- [packages/mdx/src/runtime/types.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/types.ts)
- [packages/base-ui/src/mdx.server.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/mdx.server.tsx)
- [packages/radix-ui/registry/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/registry/index.ts)
- [packages/sanity/registry/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/sanity/registry/index.ts)
- [packages/twoslash/package.json](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/package.json)
- [packages/doc-gen/package.json](https://github.com/blade47/fumadocs/blob/main/packages/doc-gen/package.json)
- [packages/mdx-remote/package.json](https://github.com/blade47/fumadocs/blob/main/packages/mdx-remote/package.json)
- [packages/content-collections/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/content-collections/src/index.ts)
- [packages/preview/src/components/provider.tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/components/provider.tsx)
- [packages/python/package.json](https://github.com/blade47/fumadocs/blob/main/packages/python/package.json)
- [packages/mdx/src/config/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/config/index.ts)
- [packages/radix-ui/package.json](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/package.json)
- [packages/asyncapi/package.json](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/package.json)
- [packages/cli/src/registry/plugins/preserve.ts](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/registry/plugins/preserve.ts)
- [packages/story/package.json](https://github.com/blade47/fumadocs/blob/main/packages/story/package.json)
Fumadocs is a modular documentation framework designed to bridge the gap between static content and dynamic, reactive documentation sites. At its core, the system solves the complexity of managing Markdown/MDX content by providing a robust pipeline that handles everything from file parsing and MDX transformation to runtime data orchestration.
The system is architected around a collection-based data model. Instead of treating files as isolated entities, Fumadocs groups them into collections, allowing for structured access via plugins. This separation of concerns—parsing and transformation in the build/runtime phase versus presentation in the UI components—allows developers to use Fumadocs across various frameworks, including Next.js, Vite, and Waku.
Central to this architecture is the `Core` package, which provides the foundational logic for source handling and schema validation. By layering specific "source" packages (like `mdx`, `local-md`, or `openapi`) on top of this core, the framework enables high-performance content delivery, incremental updates via file system watchers, and specialized integrations like TypeScript type-table generation and OpenAPI documentation.
## Core Content Orchestration
The content orchestration mechanism functions as a multi-stage pipeline that ensures data consistency across the development lifecycle. When a content collection is defined—for example, via `docsCollection` or `mdxCollection`—the framework registers hooks (like `onEmit`) to generate the necessary glue code that links the static source with the application's runtime.
The system uses a virtual file generator to create manifests. In the case of `DocsCollection`, this mechanism ensures that the relationship between documentation content and metadata is preserved and type-safe.
```mermaid
flowchart TD
Config["Configuration (source.config.ts)"] --> Core["Fumadocs Core Initialization"]
Core --> Loaders["Collection Loaders (MDX, Meta)"]
Loaders --> Emit["Emitter (Write to .source/)"]
Emit --> Runtime["Runtime Consumption (docsStore)"]
```
Sources: [packages/mdx/src/vite/index.ts:106-108](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/vite/index.ts#L106-L108), [packages/content/src/index.ts:58-69](https://github.com/blade47/fumadocs/blob/main/packages/content/src/index.ts#L58-L69)
## Build-Time vs. Runtime Content
Fumadocs employs a two-pronged strategy for content processing: static compilation for production and dynamic runtime execution for development or edge-case scenarios. The `mdx` package provides a Vite plugin interface (`mdx(config)`) that manages this transition.
During the Vite build process, the plugin initializes a `Core` instance that builds the configuration, then configures `mdxLoader` and `metaLoader` to intercept file transformations.
> [!NOTE]
> The `id.includes('virtual:vite-rsc')` guard in the Vite plugin is a crucial invariant. It prevents the MDX loader from attempting to process compiled RSC (React Server Components) client references, which would otherwise corrupt the JavaScript output stream.
Sources: [packages/mdx/src/vite/index.ts:57-86](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/vite/index.ts#L57-L86)
## Dynamic Execution Path
When running in dynamic mode (e.g., during development or when using `dynamic.ts`), content is evaluated using `executeMdx`. This avoids re-compilation for every single request by leveraging a caching mechanism in `convertLazyEntries`.
The flow for retrieving dynamic content is:
1. `getDocCollection` verifies the collection name.
2. `convertLazyEntries` registers a map of file paths to lazy compilation functions.
3. The lazy function (`body[path]`) uses `cachedResult` to ensure the compilation occurs only once per instance.
```mermaid
sequenceDiagram
participant User
participant Loader as Dynamic Loader
participant Compiler as MDX Builder
User->>Loader: doc(name, entries)
Loader->>Compiler: buildMDX(core, info)
Compiler-->>Loader: Compiled MDX Properties
Loader->>Loader: Execute and Cache
Loader-->>User: Returns Compiled Component
```
Sources: [packages/mdx/src/runtime/dynamic.ts:67-92](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/dynamic.ts#L67-L92)
## Source Integration and Schema
The `packages/core` system exposes schema definitions (`pageSchema`, `metaSchema`) that are used by all content collections. This standardizes the frontmatter structure across different content types.
| Constant | Role |
| :--- | :--- |
| `pageSchema` | Standard Zod-like schema for MDX frontmatter validation. |
| `metaSchema` | Schema for defining directory-level navigation and metadata. |
| `docsStore` | Runtime helper that wraps MDX and Meta collections into a single source. |
Sources: [packages/content/src/index.ts:6](https://github.com/blade47/fumadocs/blob/main/packages/content/src/index.ts#L6), [packages/content/src/runtime.ts:44-50](https://github.com/blade47/fumadocs/blob/main/packages/content/src/runtime.ts#L44-L50)
## Plugin System and Registry
Fumadocs supports an extensible architecture through "Registry" plugins, used primarily by the CLI for scaffolded UI components. Components are registered with a `dir` and a `files` array that maps source paths to destination targets in the consumer's project.
> [!TIP]
> Use the `pluginPreserveLayouts` helper if you are developing custom documentation layouts. It prevents the CLI from overwriting core `fumadocs-ui` layout files unless a direct installation is requested by the user.
Sources: [packages/cli/src/registry/plugins/preserve.ts:6-13](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/registry/plugins/preserve.ts#L6-L13), [packages/radix-ui/registry/index.ts:9-14](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/registry/index.ts#L9-L14)
## Design Trade-offs
The architecture makes deliberate choices to balance speed against developer experience.
| Design Choice | Benefit | Cost |
| :--- | :--- | :--- |
| Virtual Manifests | Enables rapid, decoupled builds. | Requires an internal `.source/` folder. |
| Lazy MDX Compilation | Reduces initial start-up time for large sites. | Adds latency to the first access of a page. |
| Standardized Core Schema | Guaranteed compatibility across packages. | Strict constraints on custom metadata. |
Sources: [packages/mdx/src/vite/index.ts:35](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/vite/index.ts#L35), [packages/mdx/src/runtime/dynamic.ts:91](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/dynamic.ts#L91)
## Full Worked Example: Custom Collection
To integrate a new collection that automatically includes shared remark plugins, you can use the `docsMdxCollection` utility.
```typescript
import { docsMdxCollection } from 'fumadocs-content';
import { pageSchema } from 'fumadocs-core/source/schema';
// This demonstrates how to define a collection that
// includes standard remark plugins during the bundling phase.
export const docCollection = docsMdxCollection({
dir: 'content/docs',
frontmatter: pageSchema,
options: {
remarkPlugins: [/* Add custom plugins here */],
},
});
```
Sources: [packages/content/src/index.ts:16-33](https://github.com/blade47/fumadocs/blob/main/packages/content/src/index.ts#L16-L33)
## Related
- [[Quick Start]]
- [[Project Structure]]
---
## Quick Start
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/quick-start
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/mdx/src/vite/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/vite/index.ts)
- [packages/mdx/src/next/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/next/index.ts)
- [packages/preview/src/pages/[...slugs].tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/pages/%5B...slugs%5D.tsx)
- [packages/mdx/src/runtime/dynamic.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/dynamic.ts)
- [packages/create-app/src/bin.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/bin.ts)
- [packages/create-app/src/plugins/ai.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/plugins/ai.ts)
- [packages/create-app/src/plugins/next-use-takumi.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/plugins/next-use-takumi.ts)
- [packages/content/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/content/src/index.ts)
- [packages/mdx/src/runtime/server.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/server.ts)
- [packages/preview/src/lib/source/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/lib/source/index.ts)
- [packages/preview/package.json](https://github.com/blade47/fumadocs/blob/main/packages/preview/package.json)
- [packages/preview/src/layouts/config.tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/layouts/config.tsx)
- [packages/create-app/scripts/sync.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/scripts/sync.ts)
- [packages/vite/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/vite/tsdown.config.ts)
- [packages/vite/package.json](https://github.com/blade47/fumadocs/blob/main/packages/vite/package.json)
- [packages/preview/fumadocs.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/fumadocs.config.ts)
- [packages/preview/waku.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/waku.config.ts)
- [packages/vite-data/package.json](https://github.com/blade47/fumadocs/blob/main/packages/vite-data/package.json)
- [packages/content/src/runtime.ts](https://github.com/blade47/fumadocs/blob/main/packages/content/src/runtime.ts)
- [packages/story/package.json](https://github.com/blade47/fumadocs/blob/main/packages/story/package.json)
- [packages/sanity/package.json](https://github.com/blade47/fumadocs/blob/main/packages/sanity/package.json)
- [packages/preview/src/components/provider.tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/components/provider.tsx)
- [packages/base-ui/src/provider/next.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/provider/next.tsx)
- [packages/create-app/src/constants.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/constants.ts)
- [packages/core/package.json](https://github.com/blade47/fumadocs/blob/main/packages/core/package.json)
- [packages/typescript/package.json](https://github.com/blade47/fumadocs/blob/main/packages/typescript/package.json)
- [packages/twoslash/package.json](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/package.json)
- [packages/obsidian/package.json](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/package.json)
- [packages/asyncapi/package.json](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/package.json)
- [packages/basehub/package.json](https://github.com/blade47/fumadocs/blob/main/packages/basehub/package.json)
The "Quick Start" subsystem facilitates the rapid scaffolding and initialization of Fumadocs-powered applications. Its primary goal is to abstract the complexity of configuring build tools, file-system watching, and dependency management across various framework environments (Next.js, Waku, Vite). By providing unified CLI-driven project generation and framework-specific adapter configurations, it ensures a consistent development experience regardless of the underlying stack.
The subsystem operates through a "generator-plugin" architecture. The entry point, `create-app`, interacts with the user via `clack` prompts to define project requirements, such as linting preferences, search engine solutions, and AI chat integration. Once initialized, it executes a series of plugin-based transformations—such as adding dependencies to `package.json` or injecting AI search components into layout files—ensuring the scaffolded environment adheres to project best practices.
Integration with existing projects occurs through specific framework adapters located in `mdx/src/vite/index.ts` and `mdx/src/next/index.ts`. These modules define core emitters that watch for configuration changes, handle MDX/Meta file compilation, and ensure the development server remains reactive to content updates. This design decouples the user-facing CLI experience from the complex build-time requirements of the Fumadocs framework.
## CLI Project Scaffolding
The project generation mechanism starts with the `bin.ts` entry point in the `create-app` package. It uses a structured prompt group to gather user configuration, which is then passed to the `create()` function. The CLI handles complex decisions, such as whether to enable the `/src` directory for Next.js projects or which search solution (Orama/Orama Cloud) to utilize, based on the selected template.
```typescript
// Example: CLI generation workflow in packages/create-app/src/bin.ts
await create({
packageManager: config.pm,
template: options.template,
outputDir: projectName,
installDeps: options.installDeps,
initializeGit: config.git,
plugins,
log: (message) => { info.message(message); },
});
```
Sources: [packages/create-app/src/bin.ts:272-282](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/bin.ts#L272-L282)
The system also enforces a strict pre-flight check for target directories. If a directory already exists, it prompts for deletion and handles recursive removal of files via `fs.rm` to ensure a clean slate, preventing stale configuration leaks.
Sources: [packages/create-app/src/bin.ts:319-326](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/bin.ts#L319-L326)
## Framework-Specific Adapters (MDX)
The core functionality for integrating Fumadocs into a project is split between Vite and Next.js adapters. These adapters normalize configuration and provide lifecycle hooks for the development server.
- **Next.js Adapter (`createMDX`)**: Injects custom webpack loaders for `.mdx`, `.md`, and `.json` files. It manages the `pageExtensions` configuration and initializes a `FSWatcher` to restart the development process when `configPath` changes.
- **Vite Adapter (`mdx`)**: Implements Vite-specific plugins to handle file transformations. It filters out files from internal paths like `virtual:vite-rsc` to ensure compatibility with RSC-heavy environments.
> [!NOTE]
> The dev server watcher explicitly ignores the `outDir` during initialization to prevent infinite reload loops caused by the generator emitting files into the target folder.
Sources: [packages/mdx/src/next/index.ts:138](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/next/index.ts#L138)
## Plugin Architecture
The `create-app` generator supports a pluggable architecture using `TemplatePlugin`. These plugins (such as AI chat or Image generation) provide `afterWrite` hooks that execute after the initial scaffolding is complete.
For instance, the AI Chat plugin leverages the `FumadocsComponentInstaller` to fetch components from the remote registry, then uses `ts-morph` to inspect the existing `DocsLayout` in the user's project and programmatically inject the `` trigger.
```typescript
// Example: Injecting AI Search logic in packages/create-app/src/plugins/ai.ts
const code = `
`;
// ... logic to find DocsLayout and prepend AI components
```
Sources: [packages/create-app/src/plugins/ai.ts:61-86](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/plugins/ai.ts#L61-L86)
## Hot-Reloading Mechanism
The development server for MDX content relies on a `FSWatcher` initialized inside `initServer`. This watcher triggers an `initOrReload` cycle when the `source.config.ts` file is modified.
1. `watcher.on('all')` detects file changes.
2. Checks if `path.resolve(file)` matches the known configuration path.
3. If matched, it calls `watcher.removeAllListeners()` and `watcher.close()` to prevent race conditions during the reload.
4. Executes `core.init()` with the updated configuration.
5. Recursively calls `devServer()` to restart the watch process with updated state.
Sources: [packages/mdx/src/next/index.ts:156-165](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/next/index.ts#L156-L165)
## Configuration Design Trade-offs
| Choice | Benefit | Cost |
| :--- | :--- | :--- |
| **Pluggable Scaffolding** | Highly customizable project templates | Requires complex file parsing and transformation logic |
| **Watcher-based Config** | Real-time updates without full dev-server restarts | Overhead of maintaining file system listeners |
| **Internalized Plugins** | Cohesive dependency and version management | Coupling between CLI tools and core framework |
Sources: [packages/create-app/src/bin.ts:231-270](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/bin.ts#L231-L270)
## Dynamic Runtime Initialization
The `packages/mdx/src/runtime/dynamic.ts` module provides a bridge between static content and runtime behavior. It uses an `AsyncFunction` constructor to execute compiled MDX code within a controlled, safe sandbox environment.
> [!IMPORTANT]
> The dynamic execution assumes the compiled code is safe. When passing `fullScope`, it merges user-defined `scope` with the default `jsxRuntime`, providing essential context for document hydration.
Sources: [packages/mdx/src/runtime/dynamic.ts:31-41](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/dynamic.ts#L31-L41)
This runtime dynamic loading is primarily used to serve content lazily, ensuring that large documentation sets do not overwhelm the initial memory footprint of the application. It creates internal `head` and `body` dictionaries that resolve content only upon request, effectively caching results after the first compilation of a file entry.
Sources: [packages/mdx/src/runtime/dynamic.ts:90-91](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/dynamic.ts#L90-L91)
## Related
- [[Overview]]
- [[Project Structure]]
---
## Field Key Serialization
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/field-key-serialization
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/story/src/client/arg-form.tsx](https://github.com/blade47/fumadocs/blob/main/packages/story/src/client/arg-form.tsx)
- [packages/stf/src/lib/stf.tsx](https://github.com/blade47/fumadocs/blob/main/packages/stf/src/lib/stf.tsx)
- [packages/stf/src/lib/data-engine.ts](https://github.com/blade47/fumadocs/blob/main/packages/stf/src/lib/data-engine.ts)
- [packages/stf/src/lib/utils.ts](https://github.com/blade47/fumadocs/blob/main/packages/stf/src/lib/utils.ts)
The `FieldSet` to `stringifyFieldKey` flow represents the core mechanism for identifying and reacting to UI form field updates within the `fumadocs` story system. This process translates logical data paths into stable DOM-compatible identifiers and registers reactive listeners that ensure the UI synchronizes with the underlying data store.
### Step 1: FieldSet
The `FieldSet` component serves as the structural entry point for rendering UI fields. It receives a `fieldName` (a `FieldKey` array), which acts as the canonical path to the data point within the form state.
Sources: [packages/story/src/client/arg-form.tsx:252-272](https://github.com/blade47/fumadocs/blob/main/packages/story/src/client/arg-form.tsx#L252-L272)
### Step 2: useFieldInfo
Inside `FieldSet`, the component calls `useFieldInfo` to manage dynamic metadata (like the selected index for union types). This hook bridges the gap between the raw field path and the specific configuration needed for that field's interaction state.
Sources: [packages/story/src/client/arg-form.tsx:275-275](https://github.com/blade47/fumadocs/blob/main/packages/story/src/client/arg-form.tsx#L275-L275)
### Step 3: useFieldValue
The `useFieldValue` hook is then invoked to establish a reactive binding for the data at the `fieldName` path. It extracts the current value from the `DataEngine` and ensures that any updates to this specific path trigger a local component re-render.
Sources: [packages/stf/src/lib/stf.tsx:190-237](https://github.com/blade47/fumadocs/blob/main/packages/stf/src/lib/stf.tsx#L190-L237)
### Step 4: useListener
Within `useFieldValue`, `useListener` is called to register an event listener with the `DataEngine`. This ensures that if another part of the system modifies the field, the `useFieldValue` hook receives the event and updates its internal state.
Sources: [packages/stf/src/lib/stf.tsx:225-234](https://github.com/blade47/fumadocs/blob/main/packages/stf/src/lib/stf.tsx#L225-L234)
### Step 5: DataEngine.listen
The `listen` method in `DataEngine` adds the listener instance to a `ListenerManager`. This manager maintains either a set of unindexed global listeners or a map of indexed listeners keyed by their `stringifyFieldKey` result.
Sources: [packages/stf/src/lib/data-engine.ts:127-129](https://github.com/blade47/fumadocs/blob/main/packages/stf/src/lib/data-engine.ts#L127-L129)
### Step 6: DataEngine.add
The `ListenerManager.add` method performs the registration. It specifically calls `stringifyFieldKey` on the listener's `field` property to generate a unique string key used for efficient lookups in the internal `Map`.
Sources: [packages/stf/src/lib/data-engine.ts:65-74](https://github.com/blade47/fumadocs/blob/main/packages/stf/src/lib/data-engine.ts#L65-L74)
### Step 7: stringifyFieldKey
Finally, `stringifyFieldKey` converts the `FieldKey` array into a dot-notation string (e.g., `_myField.n0`). Strings are prefixed with `_` and numbers with `n` to ensure they are valid and distinguishable when used as identifiers.
Sources: [packages/stf/src/lib/utils.ts:81-83](https://github.com/blade47/fumadocs/blob/main/packages/stf/src/lib/utils.ts#L81-L83)
> [!TIP]
> Stringification is crucial for performance because it allows the `ListenerManager` to use a native `Map` for $O(1)$ listener lookups rather than iterating over field arrays.
```mermaid
sequenceDiagram
participant FS as FieldSet
participant UFI as useFieldInfo
participant UFV as useFieldValue
participant UL as useListener
participant DE as DataEngine
participant LM as ListenerManager
participant SU as stringifyFieldKey
FS->>UFI: call(fieldName)
FS->>UFV: call(fieldName)
UFV->>UL: call(listener)
UL->>DE: listen(listener)
DE->>LM: add(listener)
LM->>SU: stringifyFieldKey(field)
SU-->>LM: return stringKey
```
Sources: [All files in trace](https://github.com/blade47/fumadocs/blob/main/)
```mermaid
flowchart TD
A[FieldSet] --> B[useFieldInfo]
A --> C[useFieldValue]
C --> D[useListener]
D --> E[DataEngine.listen]
E --> F[ListenerManager.add]
F --> G[stringifyFieldKey]
```
Sources: [All files in trace](https://github.com/blade47/fumadocs/blob/main/)
> [!NOTE]
> The transition from an array-based `FieldKey` to a string identifier happens exclusively at the registration layer (`ListenerManager`) to facilitate fast state synchronization.
### Key Observations
* **Module Boundaries:** The flow initiates in the UI layer (`packages/story`) and transitions into the core state management logic in `packages/stf`.
* **Failure Points:** A potential failure point is providing a malformed `FieldKey` to `stringifyFieldKey`. If the `FieldKey` contains unexpected types, the stringification logic may generate keys that do not match expected patterns in the `DataEngine` state object.
* **Performance:** By centralizing listener storage in a `Map` within the `DataEngine`, the system avoids expensive tree-traversal when a specific field value changes.
---
## Project Structure
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/project-structure
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/core/package.json](https://github.com/blade47/fumadocs/blob/main/packages/core/package.json)
- [packages/radix-ui/package.json](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/package.json)
- [packages/base-ui/package.json](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/package.json)
- [packages/typescript/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/typescript/src/index.ts)
- [packages/cli/src/commands/shared.ts](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/commands/shared.ts)
- [packages/base-ui/registry/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/registry/index.ts)
- [packages/sanity/registry/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/sanity/registry/index.ts)
- [packages/radix-ui/registry/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/registry/index.ts)
- [packages/doc-gen/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/doc-gen/src/index.ts)
- [packages/core/src/page-tree/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/page-tree/index.ts)
- [packages/shared/package.json](https://github.com/blade47/fumadocs/blob/main/packages/shared/package.json)
- [packages/story/src/type-tree/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/story/src/type-tree/index.ts)
- [packages/local-md/package.json](https://github.com/blade47/fumadocs/blob/main/packages/local-md/package.json)
- [packages/python/src/generated.ts](https://github.com/blade47/fumadocs/blob/main/packages/python/src/generated.ts)
- [packages/shadcn/src/types.ts](https://github.com/blade47/fumadocs/blob/main/packages/shadcn/src/types.ts)
- [package.json](https://github.com/blade47/fumadocs/blob/main/package.json)
- [packages/vite-data/package.json](https://github.com/blade47/fumadocs/blob/main/packages/vite-data/package.json)
- [packages/stf/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/stf/src/index.ts)
- [packages/mdx/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/index.ts)
- [packages/python/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/python/src/index.ts)
- [packages/api-docs/package.json](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/package.json)
- [packages/core/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/tsdown.config.ts)
- [packages/asyncapi/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/index.ts)
- [packages/typescript/src/ui/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/typescript/src/ui/index.ts)
- [packages/python/fumapy/mksource/models.py](https://github.com/blade47/fumadocs/blob/main/packages/python/fumapy/mksource/models.py)
- [packages/core/src/search/server.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/server.ts)
- [packages/create-app/src/constants.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/constants.ts)
- [pnpm-workspace.yaml](https://github.com/blade47/fumadocs/blob/main/pnpm-workspace.yaml)
- [packages/preview/src/config/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/config/index.ts)
- [packages/story/package.json](https://github.com/blade47/fumadocs/blob/main/packages/story/package.json)
Fumadocs utilizes a monorepo architecture managed via `pnpm` workspaces, designed to decouple documentation core logic, UI implementations, and ecosystem integrations. This structure solves the complexity of managing shared documentation primitives (like page trees, search indexing, and MDX processing) alongside multiple frontend UI libraries and build-tool plugins.
The project is segmented into distinct packages under the `packages/` directory. Each package serves a single responsibility: `fumadocs-core` provides the foundation, while specialized packages like `fumadocs-ui` (Radix UI based) or `base-ui` (Base UI based) offer alternative implementations of visual components. Integrations for specific ecosystems—such as MDX, TypeScript documentation, AsyncAPI, and Python source generation—are likewise separated, ensuring that a consumer only installs the dependencies required for their specific use case.
This modularity is enforced through a strict `tsdown` configuration and explicit `exports` definitions in each `package.json`. By isolating cross-cutting concerns like search, navigation, and i18n into their own sub-paths, the project minimizes bundle size for end users and allows for independent versioning and testing of components, providing a robust surface area for extensibility and integration.
## Monorepo Workspace Configuration
The workspace is managed by `pnpm-workspace.yaml`, which defines the scope of packages available for development. The root `package.json` coordinates cross-package tasks using `turbo`, facilitating global build, lint, and test cycles.
| Task | Command | Scope |
| :--- | :--- | :--- |
| Build | `turbo run build` | All packages |
| Test | `vitest` | Root test runner |
| Version | `changeset version` | Changelog management |
Sources: [pnpm-workspace.yaml:1-4](https://github.com/blade47/fumadocs/blob/main/pnpm-workspace.yaml#L1-L4), [package.json:5-17](https://github.com/blade47/fumadocs/blob/main/package.json#L5-L17)
## Core Package Architecture
`fumadocs-core` acts as the engine, housing fundamental documentation structures. It is built using `tsdown`, which generates ESM output. The core package structure emphasizes functional composition, with modules like `page-tree`, `search`, and `mdx-plugins` providing the logic for transforming source content into renderable documentation structures.
```mermaid
flowchart TD
Core["fumadocs-core"]
Core --> PT["page-tree"]
Core --> Search["search (server/client)"]
Core --> MDX["mdx-plugins"]
Core --> Framework["framework (next, waku, etc)"]
```
Sources: [packages/core/package.json:20-97](https://github.com/blade47/fumadocs/blob/main/packages/core/package.json#L20-L97), [packages/core/tsdown.config.ts:10-31](https://github.com/blade47/fumadocs/blob/main/packages/core/tsdown.config.ts#L10-L31)
## UI Component Registry
Fumadocs utilizes a registry-based approach for UI components, similar to CLI-driven distribution patterns. The `packages/base-ui` and `packages/radix-ui` registries define how components are structured, listed, and written to user codebases.
The registry logic uses a `findSlotComponents` function imported from the `shared` module to dynamically discover and register UI components. This allows the CLI to manage component installation without manual registration of every file.
```mermaid
classDiagram
class Registry {
+string name
+string dir
+Object[] components
+Object dependencies
}
```
Sources: [packages/base-ui/registry/index.ts:9-347](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/registry/index.ts#L9-L347), [packages/radix-ui/registry/index.ts:9-346](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/registry/index.ts#L9-L346)
## Content and Source Generation
Specialized packages handle non-React content sources. For instance, the `packages/python` package provides a robust interface for generating documentation from Python source code, utilizing a `ModuleInterface` that defines how docstrings and metadata are structured.
> [!NOTE]
> The `DocstringSection` type in `packages/python/src/generated.ts` uses a tagged union pattern (`kind`) to differentiate between text blocks, code examples, and admonitions, allowing the rendering engine to correctly map complex docstrings to React components.
Sources: [packages/python/src/generated.ts:43-69](https://github.com/blade47/fumadocs/blob/main/packages/python/src/generated.ts#L43-L69), [packages/python/fumapy/mksource/models.py:6-61](https://github.com/blade47/fumadocs/blob/main/packages/python/fumapy/mksource/models.py#L6-L61)
## Ecosystem Integration Strategy
The system handles environment-specific requirements through internal `peerDependenciesMeta` and `inlinedDependencies` fields. This ensures that packages can rely on necessary runtime dependencies (like `react` or `next`) while marking them as optional for consumers who might use alternative configurations.
| Strategy | Implementation Detail | Purpose |
| :--- | :--- | :--- |
| Inlining | `inlinedDependencies` | Reduce consumer bundle resolution overhead |
| Optionality | `peerDependenciesMeta` | Prevent forced installation of unused ecosystem packages |
| Aliasing | `npm:@fumadocs/base-ui` | Maintain backwards compatibility during renames |
Sources: [packages/core/package.json:162-247](https://github.com/blade47/fumadocs/blob/main/packages/core/package.json#L162-L247), [packages/base-ui/registry/index.ts:344](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/registry/index.ts#L344)
## CLI and Code Generation
The CLI tooling acts as the glue for the registry system, using `packages/cli/src/commands/shared.ts` to map internal UI names to registry keys. This allows the `create-app` package to standardize initialization across different template types (e.g., Next.js, Waku, TanStack Start).
### Lifecycle Walkthrough: Initializing an App
1. The user runs an installation command targeting a template (e.g., `+next+fuma-docs-mdx`).
2. `create-app/src/constants.ts` resolves the workspace versions for the selected template.
3. The CLI identifies the `rootProviderPath` (e.g., `app/layout.tsx`).
4. The requested registry component (from `base-ui` or `radix-ui`) is copied into the consumer directory according to the `target` path definition in the registry.
Sources: [packages/create-app/src/constants.ts:34-80](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/constants.ts#L34-L80), [packages/cli/src/commands/shared.ts:1-4](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/commands/shared.ts#L1-L4)
## Related
- [[Overview]]
- [[Quick Start]]
---
## MDX Bundling
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/mdx-bundling
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/mdx/src/bun/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/bun/index.ts)
- [packages/mdx/src/vite/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/vite/index.ts)
- [packages/mdx/src/rolldown/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/rolldown/index.ts)
- [packages/mdx/src/loaders/mdx/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/mdx/index.ts)
- [packages/mdx/src/next/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/next/index.ts)
- [packages/mdx/src/runtime/dynamic.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/dynamic.ts)
- [packages/preview/src/pages/[...slugs].tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/pages/%5B...slugs%5D.tsx)
- [packages/mdx/src/plugins/index-file.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/plugins/index-file.ts)
- [packages/mdx/package.json](https://github.com/blade47/fumadocs/blob/main/packages/mdx/package.json)
- [packages/mdx-remote/src/compile.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx-remote/src/compile.ts)
- [packages/content/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/content/src/index.ts)
- [packages/mdx/src/loaders/mdx/build-mdx.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/mdx/build-mdx.ts)
- [packages/mdx/src/loaders/mdx/remark-include.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/mdx/remark-include.ts)
- [packages/mdx/src/runtime/server.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/server.ts)
- [packages/mdx/src/loaders/mdx/remark-postprocess.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/mdx/remark-postprocess.ts)
- [packages/core/src/content/mdx/preset-runtime.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/content/mdx/preset-runtime.ts)
- [packages/local-md/src/md/compiler.ts](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/md/compiler.ts)
- [packages/content-collections/src/configuration.ts](https://github.com/blade47/fumadocs/blob/main/packages/content-collections/src/configuration.ts)
- [packages/mdx/src/config/preset.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/config/preset.ts)
- [packages/mdx/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/tsdown.config.ts)
- [packages/core/src/content/mdx/preset-bundler.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/content/mdx/preset-bundler.ts)
- [packages/content/src/runtime.ts](https://github.com/blade47/fumadocs/blob/main/packages/content/src/runtime.ts)
- [packages/mdx-remote/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx-remote/tsdown.config.ts)
- [packages/base-ui/src/mdx.server.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/mdx.server.tsx)
- [packages/vite/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/vite/tsdown.config.ts)
- [packages/mdx/src/node/loader.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/node/loader.ts)
- [packages/mdx-remote/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx-remote/src/index.ts)
- [packages/mdx-remote/src/render.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx-remote/src/render.ts)
- [packages/mdx-remote/package.json](https://github.com/blade47/fumadocs/blob/main/packages/mdx-remote/package.json)
- [packages/openapi/src/utils/document/load.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/document/load.ts)
MDX Bundling in Fumadocs is the bridge between static Markdown/MDX files and the framework-specific build pipelines (Vite, Next.js, Bun, Rolldown). It provides a unified system for transforming content, ensuring consistent parsing, plugin execution, and asset handling across different environments. By decoupling the core MDX logic from the build tool integration, it enables specialized loaders and plugins to share the same processing pipeline while respecting environment-specific requirements.
The system is architected around a central `Core` engine that manages configurations and plugins. Build-tool-specific entry points (like `createMdxPlugin` for Bun or `createMDX` for Next.js) initialize this core and register loaders for MDX and metadata files. This design ensures that regardless of the underlying bundler, the content is consistently compiled, frontmatter is transformed, and output files are emitted according to the project’s configuration.
Key responsibilities of the bundling system include resolving content files via globbing, managing the MDX compilation lifecycle (including cache hash generation and post-processing), and generating virtual entry files that provide dynamic, type-safe access to content collections. This approach simplifies development, enabling features like hot reloading for configuration files and efficient, lazy-loaded content distribution.
## Core Bundling Architecture
The bundling system relies on `createCore` as the central initialization primitive. This core maintains the global state for MDX processing, including build configurations and collection metadata. Build-tool-specific adapters bridge this core into the target environment (e.g., Vite plugins, Webpack loaders).
```mermaid
flowchart TD
A["Initialize (Core)"] --> B["Build Config (buildConfig)"]
B --> C["Plugin Context (getPluginContext)"]
C --> D["Register Loaders (MDX/Meta)"]
D --> E["Execute Build (emit)"]
```
Sources: [packages/mdx/src/core.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/core.ts#L1-L1), [packages/mdx/src/vite/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/vite/index.ts#L60-L68)
## MDX Loading Pipeline
The MDX loader is the primary mechanism for transforming `.mdx` files. It manages cache generation, frontmatter parsing, and the invocation of the build processor.
1. **Request Parsing:** The loader receives the source file and parses queries (e.g., `only=frontmatter`).
2. **Frontmatter Processing:** It extracts frontmatter and applies collection-level transformations using the core configuration.
3. **Compilation:** If full compilation is required, it invokes `buildMDX`, which uses an MDX processor configured with the environment-specific plugins.
4. **Caching:** If experimental build caching is enabled, the loader computes a hash of the content to retrieve or store compiled results, reducing redundant compilation time.
Sources: [packages/mdx/src/loaders/mdx/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/mdx/index.ts#L25-L109)
## Processor Configuration
The `buildMDX` function is the gatekeeper for processor instantiation. It utilizes a cache to store processor instances by collection and format (`md` vs `mdx`) to optimize repeated builds.
```typescript
function getProcessor(format: 'md' | 'mdx') {
const cache = core.cache as Map;
const key = `build-mdx:${collection?.name ?? 'global'}:${format}`;
let processor = cache.get(key);
// ...
processor = createProcessor({
outputFormat: 'program',
development: isDevelopment,
...mdxOptions,
remarkPlugins: [
remarkInclude,
...(mdxOptions.remarkPlugins ?? []),
[remarkPostprocess, postprocessOptions],
],
format,
});
cache.set(key, processor);
return processor;
}
```
Sources: [packages/mdx/src/loaders/mdx/build-mdx.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/mdx/build-mdx.ts#L81-L107)
## Index File Generation
The `index-file` plugin generates virtual entry points (e.g., `server.ts`, `dynamic.ts`) to provide runtime access to collections. These files contain code that invokes the server or dynamic compilation runtime to resolve and serve content efficiently.
- **Dynamic Collections:** If a collection is marked as `dynamic`, the generator uses glob patterns to identify files, extracts frontmatter, and creates lazy entry objects for runtime consumption.
- **Caching:** The index plugin utilizes a file system cache (`createFSCache`) to handle file updates efficiently, ensuring that index generation only triggers when necessary.
Sources: [packages/mdx/src/plugins/index-file.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/plugins/index-file.ts#L51-L157)
## Remark Pipeline Customization
The system uses a preset-based approach to resolve plugins for both Remark and Rehype, allowing users to extend the pipeline without replacing the core logic. `resolvePlugins` and `pluginOption` are the primary mechanisms for merging default presets with user-defined overrides.
| Plugin | Responsibility |
| :--- | :--- |
| `remarkGfm` | GFM (GitHub Flavored Markdown) support |
| `remarkHeading` | TOC generation and heading IDs |
| `remarkStructure` | Exporting `structuredData` for search indexing |
| `rehypeCode` | Code block highlighting and processing |
| `rehypeToc` | TOC integration into the rendered MDX |
Sources: [packages/core/src/content/mdx/preset-bundler.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/content/mdx/preset-bundler.ts#L38-L93), [packages/mdx/src/config/preset.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/config/preset.ts#L68-L130)
## Runtime Execution
For dynamic content, the runtime (`dynamic.ts`) provides a mechanism to execute compiled MDX code directly. This is crucial for environments where build-time pre-compilation is insufficient.
> [!IMPORTANT]
> The runtime assumes the input code is safe and executes it directly via an `AsyncFunction` constructor. This is designed for content managed within the repository, not for arbitrary user input.
```typescript
async function executeMdx(compiled: string, options: ExecuteOptions = {}) {
// ...
const hydrateFn = new AsyncFunction(...Object.keys(fullScope), compiled);
return await hydrateFn.apply(hydrateFn, Object.values(fullScope));
}
```
Sources: [packages/mdx/src/runtime/dynamic.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/dynamic.ts#L32-L45)
## Design Trade-offs
| Design Choice | Benefit | Cost |
| :--- | :--- | :--- |
| **Virtual Index Files** | Type-safe content access, dynamic loading | Complexity in file generation and cache invalidation |
| **Processor Caching** | Faster build performance across re-runs | Increased memory usage for long-running processes |
| **Preset-based Plugins** | Easy extensibility, standardized behavior | Potential plugin conflicts if not resolved carefully |
| **Async Execution** | Enables runtime content compilation/lazy loading | Requires `AsyncFunction` overhead and assumes trusted content |
Sources: [packages/mdx/src/plugins/index-file.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/plugins/index-file.ts#L49), [packages/mdx/src/loaders/mdx/build-mdx.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/mdx/build-mdx.ts#L82), [packages/mdx/src/runtime/dynamic.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/dynamic.ts#L31)
## Related
- [[MDX Plugins]]
- [[Content Storage]]
---
## Page Trees
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/page-trees
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/core/src/source/page-tree/builder.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/page-tree/builder.ts)
- [packages/radix-ui/src/components/sidebar/page-tree.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/sidebar/page-tree.tsx)
- [packages/core/src/source/page-tree/transformer-fallback.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/page-tree/transformer-fallback.ts)
- [packages/core/src/source/loader.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/loader.ts)
- [packages/radix-ui/src/utils/use-footer-items.ts](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/utils/use-footer-items.ts)
- [packages/base-ui/src/components/sidebar/page-tree.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/sidebar/page-tree.tsx)
- [packages/openapi/src/utils/pages/builder.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/pages/builder.ts)
- [packages/core/src/source/client/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/client/index.tsx)
- [packages/openapi/src/utils/pages/preset-auto.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/pages/preset-auto.ts)
- [packages/openapi/src/generate-file.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/generate-file.ts)
- [packages/asyncapi/src/generate-file.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/generate-file.ts)
- [packages/core/src/source/llms.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/llms.ts)
- [packages/core/src/mdx-plugins/remark-mdx-files.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-mdx-files.ts)
- [packages/asyncapi/src/server/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/server/index.tsx)
- [packages/asyncapi/src/utils/pages/preset-auto.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/utils/pages/preset-auto.ts)
- [packages/core/src/page-tree/utils.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/page-tree/utils.ts)
- [packages/core/src/breadcrumb.tsx](https://github.com/blade47/fumadocs/blob/main/packages/core/src/breadcrumb.tsx)
- [packages/core/src/source/storage/content.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/storage/content.ts)
- [packages/python/src/convert.ts](https://github.com/blade47/fumadocs/blob/main/packages/python/src/convert.ts)
- [packages/base-ui/src/contexts/tree.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/contexts/tree.tsx)
- [packages/radix-ui/src/contexts/tree.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/contexts/tree.tsx)
- [packages/cli/src/commands/file-tree.ts](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/commands/file-tree.ts)
- [packages/obsidian/src/build-storage.ts](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/build-storage.ts)
- [packages/base-ui/src/components/sidebar/tabs/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/sidebar/tabs/index.tsx)
- [packages/openapi/src/utils/pages/to-static-data.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/pages/to-static-data.ts)
- [packages/sanity/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/sanity/src/index.ts)
- [packages/base-ui/src/utils/use-footer-items.ts](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/utils/use-footer-items.ts)
- [packages/local-md/src/storage.ts](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/storage.ts)
- [packages/radix-ui/src/components/sidebar/tabs/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/sidebar/tabs/index.tsx)
- [packages/preview/src/lib/source/storage.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/lib/source/storage.ts)
Page Trees serve as the central hierarchical navigation model in Fumadocs, decoupling the physical organization of source files from the logical representation presented to end-users. They transform raw file-system content into a structured graph of folders, items, and separators, enabling features like nested navigation, automatic breadcrumbs, and cross-file relationship management. By formalizing this structure, the system allows for modular content scaling where documentation can be reorganized or linked without modifying the underlying source files themselves.
At its core, the Page Tree subsystem addresses the complexity of mapping filesystem-based content to web-based URLs. It handles the resolution of metadata, sorting of directory contents, and the application of custom transformations. By utilizing a "Page Tree Builder" pattern, the framework ensures that even complex documentation sets—including those spread across multiple storage providers or internationalized locales—are resolved into a unified, traversable navigation structure consistent with the project's configuration.
The subsystem integrates deeply with the UI layer, where "renderers" traverse these tree objects to generate interactive sidebar components. This separation between the construction of the tree (server-side) and its consumption (client-side) allows the platform to support both static generation and dynamic, client-side re-hydration. Through a well-defined set of visitor patterns and serialization utilities, Page Trees remain portable, efficient to navigate, and easy to extend with custom plugins.
## The Page Tree Builder Architecture
The Page Tree Builder is the engine responsible for synthesizing a `PageTree.Root` object. It operates by consuming raw file storage and applying a sequence of transformations defined by the project configuration. The build process follows a recursive descent model, starting at a defined root and visiting directories to resolve individual file nodes, metadata, and symbolic references.
A key design choice is the use of an `Attached` data structure internally during construction, which tracks state such as ownership and completion status (`SymbolUnfinished`) via JavaScript Symbols. This allows the builder to manage complex scenarios, such as when multiple folders might technically reference the same page or resource, by establishing ownership rules that prevent circular dependencies and redundant rendering.
```mermaid
flowchart TD
A["Initialize Builder"] --> B["Storage Scan
(Read all files)"]
B --> C["Build Folders Recursively"]
C --> D["Apply Transformers
(File, Folder, Separator)"]
D --> E["Finalize Root Node"]
E --> F["Strip Internal Symbols"]
```
Sources: [packages/core/src/source/page-tree/builder.ts:89-105, 140-145, 474-503](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/page-tree/builder.ts#L89-L105)
## Ownership and Reference Resolution
The subsystem implements a sophisticated ownership model to resolve how pages are assigned to folders when those pages appear in multiple metadata configurations. The `own()` function acts as a critical guard during node resolution.
> [!IMPORTANT]
> The `own()` function dictates node membership based on a `priority` integer. If a node is already owned by a higher priority process, ownership transfer is denied. This prevents race conditions or configuration conflicts where two folders compete to claim the same document index.
The resolution logic follows this sequence:
1. When a node is requested for a folder, `own(ownerPath, node, priority)` is called.
2. If the node is marked `SymbolUnfinished`, ownership resolution is rejected.
3. If the node has no owner, it is assigned to the current folder.
4. If an existing owner exists, the priority is compared; higher priority owners can displace lower priority ones by removing the node from the previous folder's children list.
Sources: [packages/core/src/source/page-tree/builder.ts:152-184](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/page-tree/builder.ts#L152-L184)
## Transformation Pipelines
Transformers allow users to intercept and modify the Page Tree structure as it is built. These are defined through an interface that allows hooks into file, folder, root, and separator generation.
| Transformer Hook | Signature | Purpose |
| :--- | :--- | :--- |
| `file` | `(node: PageTree.Item) => PageTree.Item` | Modify page links or metadata |
| `folder` | `(node: PageTree.Folder) => PageTree.Folder` | Customize folder names or structure |
| `separator` | `(node: PageTree.Separator) => PageTree.Separator` | Modify navigational separators |
| `root` | `(node: PageTree.Root) => PageTree.Root` | Global post-processing of the tree |
Sources: [packages/core/src/source/page-tree/builder.ts:18-28](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/page-tree/builder.ts#L18-L28)
## Tree Traversal and Utilities
To facilitate the consumption of trees (for breadcrumbs, finding neighbors, or flattening the structure), the framework provides a suite of utility functions centered on the `visit()` function. This function performs a depth-first search on the tree nodes.
```typescript
// Example: Using visit to modify icon rendering during tree traversal
import { visit } from 'fumadocs-core/page-tree/utils';
const transformedTree = visit(myTree, (node) => {
if (node.type === 'page' && node.icon) {
// Custom processing logic
}
});
```
> [!TIP]
> The `visit()` function accepts return values `'skip'` and `'break'`. Returning `'skip'` prevents recursion into children, while `'break'` immediately exits the entire traversal, which is essential for performance when searching large documentation trees.
Sources: [packages/core/src/page-tree/utils.ts:176-220](https://github.com/blade47/fumadocs/blob/main/packages/core/src/page-tree/utils.ts#L176-L220)
## Client-Side Serialization
Since Page Trees are generated on the server (often via React Server Components), they must be serialized before reaching the client. The subsystem includes `serializePageTree` and `deserializePageTree` functions to bridge this gap.
```mermaid
sequenceDiagram
participant S as Server
participant C as Client
S->>S: visit(tree) to strip non-serializable objects
S->>C: Transmit SerializedPageTree
C->>C: deserializePageTree(data)
C->>C: Re-inflate with react-dom/server
```
Sources: [packages/core/src/source/client/index.tsx:14-39](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/client/index.tsx#L14-L39)
## Sidebar Renderer Architecture
Sidebar components in the Radix-UI and Base-UI layers utilize a renderer-context pattern. They define an `internalComponents` set that maps tree node types to actual React elements. The `createPageTreeRenderer` function takes these dependencies and returns a function that manages the rendering lifecycle of the tree.
| Component | Node Type | Purpose |
| :--- | :--- | :--- |
| `SidebarSeparator` | `separator` | Visual breaks in navigation |
| `SidebarFolder` | `folder` | Expandable navigational grouping |
| `SidebarItem` | `page` | Leaf-level documentation links |
Sources: [packages/radix-ui/src/components/sidebar/page-tree.tsx:31-98](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/sidebar/page-tree.tsx#L31-L98), [packages/base-ui/src/components/sidebar/page-tree.tsx:31-98](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/sidebar/page-tree.tsx#L31-L98)
## Design Trade-offs
| Design Choice | Benefit | Cost |
| :--- | :--- | :--- |
| Recursive building | Intuitive directory mirroring | Potential stack depth issues with deep trees |
| WeakMap-based cache | Memory-efficient node access | Requires objects as keys, sensitive to identity |
| Symbol-based tagging | Non-enumerable, non-colliding hidden state | More verbose access than plain object properties |
| Separated builders | Allows isolated locale-based trees | Requires careful synchronization for cross-tree links |
Sources: [packages/core/src/source/page-tree/builder.ts:74-82, 93-96](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/page-tree/builder.ts#L74-L82)
## Related
- [[Content Storage]]
- [[Sidebar Navigation]]
---
## MDX Plugins
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/mdx-plugins
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/core/src/mdx-plugins/remark-structure.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-structure.ts)
- [packages/core/src/mdx-plugins/remark-steps.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-steps.ts)
- [packages/typescript/src/lib/remark-auto-type-table.ts](https://github.com/blade47/fumadocs/blob/main/packages/typescript/src/lib/remark-auto-type-table.ts)
- [packages/core/src/mdx-plugins/remark-llms.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-llms.ts)
- [packages/core/src/content/md.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/content/md.ts)
- [packages/core/src/mdx-plugins/remark-code-tab.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-code-tab.ts)
- [packages/core/src/mdx-plugins/remark-block-id.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-block-id.ts)
- [packages/obsidian/src/mdx/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/mdx/index.ts)
- [packages/core/src/mdx-plugins/rehype-code.core.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/rehype-code.core.ts)
- [packages/mdx/src/loaders/mdx/remark-postprocess.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/mdx/remark-postprocess.ts)
- [packages/core/src/mdx-plugins/rehype-toc.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/rehype-toc.ts)
- [packages/mdx/src/loaders/mdx/remark-include.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/mdx/remark-include.ts)
- [packages/core/src/mdx-plugins/remark-feedback-block.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-feedback-block.ts)
- [packages/core/src/mdx-plugins/remark-mdx-files.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-mdx-files.ts)
- [packages/core/src/mdx-plugins/remark-admonition.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-admonition.ts)
- [packages/core/src/mdx-plugins/remark-npm.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-npm.ts)
- [packages/core/src/mdx-plugins/remark-heading.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-heading.ts)
- [packages/obsidian/src/remark/remark-block-id.ts](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/remark/remark-block-id.ts)
- [packages/core/src/mdx-plugins/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/index.ts)
- [packages/core/src/content/mdx/preset-runtime.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/content/mdx/preset-runtime.ts)
- [packages/core/src/mdx-plugins/rehype-code/parsers.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/rehype-code/parsers.ts)
- [packages/obsidian/src/remark/remark-wikilinks.ts](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/remark/remark-wikilinks.ts)
- [packages/core/src/content/mdx/preset-bundler.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/content/mdx/preset-bundler.ts)
- [packages/core/src/mdx-plugins/remark-llms.runtime.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-llms.runtime.ts)
- [packages/doc-gen/src/remark-install.ts](https://github.com/blade47/fumadocs/blob/main/packages/doc-gen/src/remark-install.ts)
- [packages/preview/src/lib/md.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/lib/md.ts)
- [packages/core/src/mdx-plugins/remark-mdx-mermaid.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-mdx-mermaid.ts)
- [packages/mdx/src/core.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/core.ts)
- [packages/core/src/mdx-plugins/stringifier.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/stringifier.ts)
- [packages/content-collections/src/configuration.ts](https://github.com/blade47/fumadocs/blob/main/packages/content-collections/src/configuration.ts)
MDX Plugins act as the foundational processing layer within the Fumadocs documentation ecosystem, sitting directly atop the `unified`, `remark`, and `rehype` infrastructure. Their purpose is to augment standard Markdown and MDX documents with domain-specific features required for technical documentation, such as automated structure extraction, advanced code block handling, and syntax enrichment for complex UI elements like tabbed code blocks, file trees, and admonitions.
By operating as standard plugins for the `unified` ecosystem, they enable a modular pipeline architecture where each plugin performs a targeted transformation on the Abstract Syntax Tree (AST). This design decouples documentation business logic from core parsing, allowing developers to opt-in to specific capabilities like automatic TOC generation, type table insertion for TypeScript interfaces, or Obsidian-style wikilinks.
The subsystem interacts heavily with the file system during build-time via loaders and content-collections to resolve imports, glob patterns, and frontmatter. At its core, the plugin system prioritizes predictability and performance by transforming the AST during the compilation lifecycle, ensuring that the heavy lifting—such as static code analysis for type tables or AST restructuring for tabs—happens once during build/bundle time, rather than at runtime.
## Core Transformation Pipeline
The MDX processor relies on a sequence of plugins that transform the document state from raw text into a rich component representation. The pipeline consists of two primary stages: Remark (Markdown AST) and Rehype (HTML AST).
```mermaid
flowchart TD
A["Raw Markdown/MDX"] --> B["Remark Processors
(AST Manipulation)"]
B --> C["remark-gfm, remark-heading, etc."]
C --> D["Rehype Processors
(HTML/JSX Transformation)"]
D --> E["rehype-code, rehype-toc"]
E --> F["Final JSX Runtime Output"]
```
Sources: [packages/core/src/content/mdx/preset-bundler.ts:38-76](https://github.com/blade47/fumadocs/blob/main/packages/content-collections/src/configuration.ts#L38-L76), [packages/core/src/content/mdx/preset-runtime.ts:35-76](https://github.com/blade47/fumadocs/blob/main/packages/core/src/content/mdx/preset-runtime.ts#L35-L76)
The order of plugin registration is critical. Because plugins like `remarkStructure` and `remarkHeading` depend on the ID properties generated during the heading pass, they must be registered in the correct sequence. The presets (`preset-runtime.ts` and `preset-bundler.ts`) enforce this ordering to maintain invariant consistency across the transformation chain.
## Code Block Transformation Mechanism
The code block subsystem utilizes a specialized transformer approach to convert standard fenced code blocks into enriched JSX components. It leverages Shiki for syntax highlighting while injecting complex functionality like tabs, icons, and line numbering through specialized `ShikiTransformer` instances created in `rehype-code.core.ts`.
- **Parsing Phase**: The system intercepts the document tree and delegates to the highlighter to generate the highlighted HAST (HTML Abstract Syntax Tree).
- **Transformation Phase**: During the processing step, it injects the transformer suite.
- **Enrichment**: The `transformerTab` function adds logic to identify if a code block should be wrapped into a `` component based on meta-string metadata.
```mermaid
sequenceDiagram
participant P as Code Processor
participant S as Shiki Highlighter
participant T as Transformers (Icon/Tab/Diff)
P->>S: getOrInit()
P->>T: initTransformer()
P->>S: loadLanguage() / loadTheme()
S-->>P: Ready
P->>P: rehypeShikiFromHighlighter(tree, transformers)
```
Sources: [packages/core/src/mdx-plugins/rehype-code.core.ts:82-142](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/rehype-code.core.ts#L82-L142)
> [!WARNING]
> When using `tab="value"` in code blocks, you must ensure `remarkCodeTab` is configured in the pipeline. Using the legacy approach in the rehype pipeline (via `transformerTab`) will trigger a warning, as that path is deprecated in favor of the more robust `remarkCodeTab` transformation logic.
## Tabulated Data: Plugin Selection and Priority
Plugins often perform complex lookups to determine how to process a specific block. The following table summarizes key plugins that employ prioritization logic:
| Plugin | Selection Logic | Tie-break / Priority |
| :--- | :--- | :--- |
| `remarkCodeTab` | Checks for `tab` or `tab-group` metadata | Sequence order in parent node children |
| `remarkSteps` | Checks `data-fd-step` existence | `currentStep` incrementer |
| `rehypeToc` | `HeadingTags` (h1-h6) | Document index order |
| `remarkStructure` | Configured `types` array | Depth-first search (visit) |
Sources: [packages/core/src/mdx-plugins/remark-code-tab.ts:248-268](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-code-tab.ts#L248-L268), [packages/core/src/mdx-plugins/remark-steps.ts:110-133](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-steps.ts#L110-L133), [packages/core/src/mdx-plugins/rehype-toc.ts:70-108](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/rehype-toc.ts#L70-L108)
## Step Processing Mechanism
The `remarkSteps` plugin converts headings into interactive step components by transforming consecutive headings of the same depth into a single `mdxJsxFlowElement` container.
1. **Iteration**: It visits every node and identifies headings.
2. **Identification**: It uses `handleHeadingStep` to check if a heading matches the regex `/^(\d+)\.\s(.+)$/` or carries the `[step]` tag.
3. **Encapsulation**: When a block of steps is found, `convertToSteps` is called to group them into a `fd-steps` div, mapping children into `fd-step` containers.
> [!IMPORTANT]
> The `remarkSteps` plugin requires a strict invariant: if a heading depth changes within a detected block, the `onEnd()` function is triggered immediately, effectively closing the current step group. This prevents deeply nested or mismatched headings from being incorrectly captured.
Sources: [packages/core/src/mdx-plugins/remark-steps.ts:100-133](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-steps.ts#L100-L133)
## Stringification and Context Handling
The `defaultStringifier` provides a unified way to turn MDAST nodes back into plain text, which is vital for search indexing, LLM context generation, and structured data extraction. It wraps the `mdast-util-to-markdown` utility and adds context-aware handler support.
- **Handlers**: These allow customization of how nodes (e.g., `link`, `heading`, `image`) are converted to strings.
- **Context Injection**: By utilizing a provided `Context` type, the stringifier allows custom handlers (e.g., in `remark-structure`) to relay metadata (like `addContent`) during the traversal of the syntax tree.
Sources: [packages/core/src/mdx-plugins/stringifier.ts:237-257](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/stringifier.ts#L237-L257)
## Design Trade-offs
| Design Choice | Benefit | Cost |
| :--- | :--- | :--- |
| **AST Transformation** | High performance, build-time static generation | Complex plugin order dependencies |
| **Unified Handlers** | Consistent output for different use cases | Handler pollution (logic spread across multiple plugins) |
| **MDX Custom Element Injection** | Enables rich UI components in documentation | Requires MDX compiler support (or `unified` plugin intervention) |
Sources: [packages/core/src/mdx-plugins/stringifier.ts:116-174](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/stringifier.ts#L116-L174), [packages/core/src/mdx-plugins/remark-structure.ts:152-199](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-structure.ts#L152-L199)
## Full Worked Example: Generating Structured Data
This snippet shows how a manual call to `structure` (an export of `remarkStructure`) processes content into a structured data object.
```typescript
import { structure } from 'fumadocs-core/mdx-plugins/remark-structure';
const content = `
# Getting Started
This is the intro.
## Installation
Run \`npm install\`.
`;
const data = structure(content);
// Returns:
// {
// headings: [{ id: 'getting-started', content: 'Getting Started' }, ...],
// contents: [{ heading: 'getting-started', content: 'This is the intro.' }, ...]
// }
```
Sources: [packages/core/src/mdx-plugins/remark-structure.ts:216-229](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-structure.ts#L216-L229)
## Related
- [[MDX Bundling]]
---
## Remote MDX
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/remote-mdx
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/mdx/src/loaders/mdx/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/mdx/index.ts)
- [packages/mdx/src/runtime/dynamic.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/dynamic.ts)
- [packages/mdx/src/plugins/index-file.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/plugins/index-file.ts)
- [packages/preview/src/pages/[...slugs].tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/pages/%5B...slugs%5D.tsx)
- [packages/mdx/src/node/_loader.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/node/_loader.ts)
- [packages/core/src/source/dynamic.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/dynamic.ts)
- [packages/core/src/content/md.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/content/md.ts)
- [packages/local-md/src/md/renderer.ts](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/md/renderer.ts)
- [packages/local-md/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/index.ts)
- [packages/mdx/src/webpack/mdx.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/webpack/mdx.ts)
- [packages/mdx/src/webpack/meta.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/webpack/meta.ts)
- [packages/mdx-remote/src/compile.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx-remote/src/compile.ts)
- [packages/mdx/src/runtime/browser.tsx](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/browser.tsx)
- [packages/core/src/mdx-plugins/remark-llms.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-llms.ts)
- [packages/mdx/src/runtime/server.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/server.ts)
- [packages/mdx/src/loaders/mdx/remark-include.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/mdx/remark-include.ts)
- [packages/mdx-remote/src/render.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx-remote/src/render.ts)
- [packages/mdx-remote/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx-remote/src/index.ts)
- [packages/mdx/src/loaders/mdx/build-mdx.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/mdx/build-mdx.ts)
- [packages/core/src/content/mdx/preset-runtime.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/content/mdx/preset-runtime.ts)
- [packages/asyncapi/src/ui/components/markdown.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/components/markdown.tsx)
- [packages/mdx-remote/src/client/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx-remote/src/client/index.ts)
- [packages/openapi/src/ui/components/markdown.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/components/markdown.tsx)
- [packages/local-md/src/md/compiler.ts](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/md/compiler.ts)
- [packages/preview/src/components/markdown.tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/components/markdown.tsx)
- [packages/core/src/mdx-plugins/remark-llms.runtime.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-llms.runtime.ts)
- [packages/mdx/src/core.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/core.ts)
- [packages/content-collections/src/configuration.ts](https://github.com/blade47/fumadocs/blob/main/packages/content-collections/src/configuration.ts)
- [packages/preview/src/lib/md.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/lib/md.ts)
- [packages/mdx/src/utils/fs-cache.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/utils/fs-cache.ts)
Remote MDX is a specialized subsystem within the Fumadocs ecosystem that enables the dynamic compilation and execution of MDX content, typically outside of the primary build-time bundler context. By decoupling the MDX processor from static build steps, it allows for on-demand rendering, remote content fetching, and runtime evaluation of Markdown documents. This is essential for applications requiring highly flexible content delivery, such as CMS-backed documentation or platform-agnostic content rendering where source files may not be available to the compiler until runtime.
The subsystem bridges the gap between raw text content (stored as strings or files) and React-based UI components. It achieves this by providing a unified `dynamic` entry point that handles the initialization of the compiler core, mapping document collections to lazy-loading entries, and executing the compiled MDX via custom-built `AsyncFunction` wrappers. This architecture supports advanced features like frontmatter transformation, cached file reads, and structured data extraction, ensuring consistency between build-time and runtime content environments.
Interacting with the broader Fumadocs system, Remote MDX leverages existing remark and rehype plugin pipelines. Its design favors "pre-compilation" where possible—compiling content into a serialized JavaScript format—which allows runtime components to simply hydrate the result rather than re-running heavy transformation chains. This performance-oriented approach ensures that even "dynamic" content maintains the efficiency expected of statically generated documentation.
## Core Compilation Mechanism
The Remote MDX pipeline processes content by transforming raw source strings into executable JavaScript representations. This process is orchestrated by `buildMDX`, which utilizes `@mdx-js/mdx` to create a `Processor` instance configured according to the global Fumadocs settings.
```mermaid
flowchart TD
A["Raw Content"] --> B["frontmatter()"]
B --> C["buildMDX()"]
C --> D["MDX Processor"]
D --> E["Compiled Program String"]
E --> F["executeMdx()"]
F --> G["React Components"]
```
Sources: [packages/mdx/src/runtime/dynamic.ts:75-81](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/dynamic.ts#L75-L81), [packages/mdx/src/loaders/mdx/build-mdx.ts:92-102](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/mdx/build-mdx.ts#L92-L102)
The `buildMDX` function is the primary gatekeeper, ensuring that all plugins and post-processing steps (such as `remark-include`) are applied correctly before the content is serialized. The `executeMdx` helper then uses an `AsyncFunction` constructor to instantiate the compiled code string into a runnable object. This allows the system to treat the output of an MDX file as a standard, dynamic module that can be injected with specific React scopes and runtimes.
## Dynamic Collection Handling
The `dynamic` utility coordinates how individual documents within a collection are exposed for runtime access. By using `convertLazyEntries`, it wraps file content processing into asynchronous factory functions (`head` and `body` getters).
> [!NOTE]
> The `body` getter uses a caching strategy where `(cachedResult ??= compile(entry))` ensures that a specific document is only compiled once per lifecycle, preventing redundant expensive CPU operations during concurrent requests.
Sources: [packages/mdx/src/runtime/dynamic.ts:91-91](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/dynamic.ts#L91-L91)
The system distinguishes between `doc` and `docs` collection types, applying appropriate metadata resolution via `getDocCollection`. When an entry is accessed, it triggers the file-system-based compilation process, reading the physical file from `info.fullPath` and processing it through the runtime MDX builder.
## Runtime Execution Architecture
The runtime execution layer uses the `executeMdx` function to handle the final conversion of compiled strings to interactive components. It establishes an isolated scope by merging user-provided context with the default JSX runtime.
```mermaid
sequenceDiagram
participant Source as Compiled MDX String
participant Exec as executeMdx()
participant Runtime as JSX Runtime
Source->>Exec: compiled body
Exec->>Exec: Construct fullScope
Exec->>Runtime: hydrateFn.apply(..., scope)
Runtime-->>Exec: React Components
Exec-->>Source: Rendered Output
```
Sources: [packages/mdx/src/runtime/dynamic.ts:32-45](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/dynamic.ts#L32-L45)
By creating a function via `new AsyncFunction(...Object.keys(fullScope), compiled)`, the system dynamically generates a closure that treats the MDX-compiled source as if it were a local module. This mechanism bypasses the need for manual file system writes in high-performance runtime environments.
## Index File Generation and Emission
The `index-file` plugin is responsible for generating the glue code that bridges static configuration with dynamic entry points. It works by inspecting the internal `Core` state and emitting TypeScript files that register collections to the runtime.
| Choice | Benefit | Cost |
| :--- | :--- | :--- |
| **Code Generation** | Type safety, static optimization | Adds complexity to build step |
| **Lazy Globbing** | Minimal memory overhead | Requires runtime resolution |
| **Caching Cache Dir** | Faster re-compilations | Disk I/O overhead |
Sources: [packages/mdx/src/plugins/index-file.ts:121-157](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/plugins/index-file.ts#L121-L157)
The `emit` loop systematically creates `server.ts`, `dynamic.ts`, and `browser.ts` entry points. The selection of which collections to include is performed by evaluating `isDynamic(collection)`, a check that ensures only collections specifically flagged for runtime compilation are subjected to the dynamic index generation logic.
## Frontmatter and Content Invariants
Remote MDX maintains strict invariants when handling frontmatter, especially in dynamic collections. The `core.transformFrontmatter` method is the centralized point for this, ensuring that data validation (via Zod schemas) and plugin-based transformations are always applied before the data touches the compiler.
> [!CAUTION]
> If a collection defines a `schema`, it MUST pass validation during the `transformFrontmatter` phase. Failure to do so at runtime will result in an `Error` thrown early in the pipeline, preventing partially malformed data from ever entering the rendering loop.
Sources: [packages/mdx/src/core.ts:153-169](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/core.ts#L153-L169)
## Integration Example
Below is a simplified example demonstrating how one would invoke the dynamic loader to access a document collection at runtime.
```typescript
import { dynamic } from 'fumadocs-mdx/runtime/dynamic';
// 1. Initialized with config exports, core options, and server runtime settings
const runtime = await dynamic(
configExports,
{ environment: 'runtime', configPath: './source.config.ts', outDir: '.source' },
{ doc: { passthroughs: ['extractedReferences'] } }
);
// 2. Fetch a document from the collection
const docEntry = await runtime.doc('blog', '/base/path', [
{
info: { path: 'example.mdx', fullPath: './content/example.mdx' },
data: { title: 'Hello World' }
}
]);
```
Sources: [packages/mdx/src/runtime/dynamic.ts:47-123](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/dynamic.ts#L47-L123)
## Related
- [[MDX Bundling]]
---
## Content Storage
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/content-storage
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/mdx/src/loaders/mdx/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/mdx/index.ts)
- [packages/core/src/source/loader.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/loader.ts)
- [packages/core/src/source/dynamic.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/dynamic.ts)
- [packages/local-md/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/index.ts)
- [packages/core/src/source/storage/content.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/storage/content.ts)
- [packages/preview/src/lib/source/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/lib/source/index.ts)
- [packages/mdx/src/webpack/meta.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/webpack/meta.ts)
- [packages/content/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/content/src/index.ts)
- [packages/mdx/src/loaders/meta.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/meta.ts)
- [packages/local-md/src/storage.ts](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/storage.ts)
- [packages/preview/src/lib/source/storage.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/lib/source/storage.ts)
- [packages/mdx/src/runtime/server.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/server.ts)
- [packages/core/package.json](https://github.com/blade47/fumadocs/blob/main/packages/core/package.json)
- [packages/mdx/src/loaders/mdx/remark-include.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/mdx/remark-include.ts)
- [packages/core/src/source/storage/file-system.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/storage/file-system.ts)
- [packages/obsidian/src/build-storage.ts](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/build-storage.ts)
- [packages/asyncapi/src/server/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/server/index.tsx)
- [packages/openapi/src/server/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/server/index.tsx)
- [packages/preview/src/pages/_api/img.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/pages/_api/img.ts)
- [packages/core/src/source/source.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/source.ts)
- [packages/obsidian/src/build-resolver.ts](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/build-resolver.ts)
- [packages/core/src/source/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/index.ts)
- [packages/content/src/runtime.ts](https://github.com/blade47/fumadocs/blob/main/packages/content/src/runtime.ts)
- [packages/mdx/src/loaders/config.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/config.ts)
- [packages/content-collections/src/configuration.ts](https://github.com/blade47/fumadocs/blob/main/packages/content-collections/src/configuration.ts)
- [packages/core/src/source/types.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/types.ts)
- [packages/content/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/content/tsdown.config.ts)
- [packages/content-collections/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/content-collections/src/index.ts)
- [packages/core/src/source/schema.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/schema.ts)
- [packages/local-md/src/shared.ts](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/shared.ts)
Content Storage provides the foundational abstraction layer that reconciles heterogeneous content sources—such as local Markdown/MDX files, Obsidian vaults, and OpenAPI or AsyncAPI specifications—into a normalized virtual file system. By decoupling the source of truth from the consumption layer (the documentation UI), it enables seamless ingestion of content regardless of whether it originates from the local disk, dynamic remote schemas, or content collection build pipelines.
The architecture centers on the `FileSystem` primitive, which serves as an in-memory repository for virtualized files. Content loaders (such as `local-md`, `mdx`, or protocol-specific plugins like `openapi`) map raw source files into uniform `ContentStoragePageFile` or `ContentStorageMetaFile` structures. This normalization is essential for the `loader` utility, which acts as the primary orchestrator, indexing these files and generating a queryable API (e.g., for page tree construction, routing, and metadata retrieval).
By standardizing access via a shared `ContentStorage` interface, the system supports sophisticated features like internationalization (i18n), workspace scoping, and dynamic revalidation. This unified model ensures that components relying on the content tree do not need to be aware of the underlying file origin, effectively treating all content sources as a singular, consistent, and indexable collection.
## The Virtual File System Architecture
The `FileSystem` class defines the structural core of the content storage system. It maintains an in-memory representation of files and folders, enabling efficient lookups without constant disk I/O. The system uses a virtual file path system where nested folders are tracked, allowing the content loader to walk the tree recursively during page tree generation.
```mermaid
classDiagram
class FileSystem {
+Map files
+Map folders
+read(path)
+write(path, file)
+makeDir(path)
}
class ContentStorage {
<>
+MetaFile
+PageFile
}
FileSystem <|-- ContentStorage
```
Sources: [packages/core/src/source/storage/file-system.ts:6-88](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/storage/file-system.ts#L6-L88), [packages/core/src/source/storage/content.ts:6-14](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/storage/content.ts#L6-L14)
## Content Storage Builders
The `createContentStorageBuilder` utility orchestrates the ingestion of raw `StaticSource` input into a `FileSystem` instance. It handles path normalization and locale-specific partitioning based on `i18n` configuration. This builder performs a crucial transformation: it strips locale prefixes from file paths and places them into partitioned virtual filesystems, ensuring that the remainder of the system treats multilingual content as unified sets.
The process of populating the storage involves iterating over source files, normalizing their paths, and applying `ContentStorage` transformations. The `parser` function inside the builder detects the locale—either via directory structure (`dir`) or file naming conventions—and groups files accordingly before instantiating the `FileSystem`.
Sources: [packages/core/src/source/storage/content.ts:50-87](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/storage/content.ts#L50-L87)
## Indexing Mechanism
Once the `FileSystem` is populated, the `createPageIndexer` utility builds an index for performant content discovery. This indexer maintains multiple internal maps that cross-reference files by path and slug. It is the core service that enables path resolution, locale switching, and page tree node lookups (e.g., retrieving a page's metadata from a node tree).
- **Page Indexing**: Uses a combined key of `[lang].[slug]` to provide O(1) lookups for specific documents.
- **Path Resolution**: Maps `[lang].[path]` for both metadata (`pathToMeta`) and page contents (`pathToPage`), allowing the system to resolve relative paths during content rendering.
```mermaid
flowchart TD
A[Raw Source Files] --> B[Builder]
B --> C[Partitioned
FileSystems]
C --> D[PageIndexer]
D --> E[Page Lookup Map]
D --> F[Path to Meta Map]
D --> G[Path to Page Map]
```
Sources: [packages/core/src/source/loader.ts:178-246](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/loader.ts#L178-L246)
## Plugin Integration
Plugins extend the storage lifecycle at two distinct points. The `transformStorage` hook allows plugins to modify or filter the file set after it has been loaded into a `FileSystem` instance, while `transformPageTree` handles structural adjustments at the node generation phase.
| Plugin Hook | Responsibility |
| :--- | :--- |
| `config` | Modifies loader settings before initialization |
| `transformStorage` | Manipulates the filesystem state (e.g., slug normalization) |
| `transformPageTree` | Modifies the final hierarchy node attributes |
Sources: [packages/core/src/source/loader.ts:501-525](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/loader.ts#L501-L525)
> [!TIP]
> The `priorityMap` used by `buildPlugins` ensures that `pre` plugins execute before standard ones, while `post` plugins execute last, enabling deterministic control over how storage is transformed. Specifically, `priorityMap` values (`pre: 1`, `default: 0`, `post: -1`) determine the sort order.
Sources: [packages/core/src/source/loader.ts:532-551](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/loader.ts#L532-L551)
## Dynamic Source Invalidation
For non-static sources (like remote OpenAPI schemas or local dev servers), the system employs a cache-invalidation mechanism. The `DynamicLoader` keeps a `sourceCache` to track remote inputs. When a change event triggers `invalidate` or `revalidate`, the system clears the cache, forcing the loader to re-fetch and re-index the content.
> [!WARNING]
> In `local-md` integrations, `devServer` connections use an event subscription model (`conn.subscribe`) to call `invalidateFile`. This ensures that cache consistency is maintained across HMR cycles, but it requires that files are resolved via `path.resolve` to avoid path mismatch errors.
Sources: [packages/core/src/source/dynamic.ts:81-114](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/dynamic.ts#L81-L114), [packages/local-md/src/index.ts:168-195](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/index.ts#L168-L195)
## Lifecycle Walkthrough: Initializing the Loader
When `loader()` is called, it enters a multi-step sequence to construct the operational state:
1. **Config Resolution**: `resolveConfig` merges options and initializes plugin chains.
2. **Storage Building**: `createContentStorageBuilder` is invoked. If i18n is enabled, it returns a record of multiple `ContentStorage` instances (one per language); otherwise, it returns a `single()` instance.
3. **Indexing**: The loader iterates through the storage instances, passing them to `indexer.scan`.
4. **Tree Building**: The `getPageTrees` function is invoked on-demand (lazy) to compute the hierarchical tree using the loaded `FileSystem` and configured `PageTreeTransformer`s.
Sources: [packages/core/src/source/loader.ts:300-346](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/loader.ts#L300-L346)
```mermaid
sequenceDiagram
participant User
participant Loader
participant StorageBuilder
participant Indexer
User->>Loader: call loader(input, options)
Loader->>StorageBuilder: createContentStorageBuilder(config)
StorageBuilder-->>Loader: returns storage instance(s)
Loader->>Indexer: indexer.scan(storage)
Indexer-->>Loader: populates path/slug maps
Note over Loader: Now ready to resolve paths and generate trees
```
Sources: [packages/core/src/source/loader.ts:300-314](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/loader.ts#L300-L314)
## Related
- [[Page Trees]]
---
## Obsidian Vaults
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/obsidian-vaults
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/obsidian/src/remark/remark-convert.ts](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/remark/remark-convert.ts)
- [packages/obsidian/src/mdx/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/mdx/index.ts)
- [packages/core/src/source/page-tree/transformer-fallback.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/page-tree/transformer-fallback.ts)
- [packages/mdx/src/loaders/mdx/remark-include.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/mdx/remark-include.ts)
- [packages/core/src/mdx-plugins/remark-structure.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-structure.ts)
- [packages/core/src/mdx-plugins/remark-steps.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-steps.ts)
- [packages/obsidian/src/remark/remark-wikilinks.ts](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/remark/remark-wikilinks.ts)
- [packages/obsidian/src/build-resolver.ts](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/build-resolver.ts)
- [packages/obsidian/src/remark/remark-block-id.ts](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/remark/remark-block-id.ts)
- [packages/obsidian/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/index.ts)
- [packages/obsidian/src/build-storage.ts](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/build-storage.ts)
- [packages/core/src/mdx-plugins/remark-llms.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-llms.ts)
- [packages/core/src/source/llms.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/llms.ts)
- [packages/core/src/mdx-plugins/remark-feedback-block.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-feedback-block.ts)
- [packages/core/package.json](https://github.com/blade47/fumadocs/blob/main/packages/core/package.json)
- [packages/preview/src/lib/source/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/lib/source/index.ts)
- [packages/mdx/src/loaders/mdx/remark-postprocess.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/mdx/remark-postprocess.ts)
- [packages/obsidian/src/remark/remark-obsidian-comment.ts](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/remark/remark-obsidian-comment.ts)
- [packages/obsidian/src/remark/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/remark/index.ts)
- [packages/obsidian/src/convert.ts](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/convert.ts)
- [packages/core/src/source/storage/content.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/storage/content.ts)
- [packages/obsidian/src/utils/get-refs.ts](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/utils/get-refs.ts)
- [packages/twoslash/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/src/index.ts)
- [packages/obsidian/src/read-vaults.ts](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/read-vaults.ts)
- [packages/python/src/convert.ts](https://github.com/blade47/fumadocs/blob/main/packages/python/src/convert.ts)
- [packages/content-collections/src/configuration.ts](https://github.com/blade47/fumadocs/blob/main/packages/content-collections/src/configuration.ts)
- [packages/core/src/mdx-plugins/remark-llms.runtime.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-llms.runtime.ts)
- [packages/obsidian/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/tsdown.config.ts)
- [packages/core/src/search/orama-cloud-legacy.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama-cloud-legacy.ts)
- [packages/preview/src/lib/source/storage.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/lib/source/storage.ts)
Obsidian Vaults in the context of Fumadocs serves as a bridging mechanism to transform standard, local Obsidian note repositories into structured, web-ready documentation content. It solves the compatibility problem between the idiosyncratic markdown features used by Obsidian users (such as Wikilinks, callouts, block IDs, and comments) and the standard MDX/Remark pipeline used by modern static site generators.
The component architecture is designed to intercept raw file collections, parse them into a virtualized storage model, and apply specialized Remark transformations. By centralizing the resolution of cross-file references and syntax conversion, Obsidian Vaults allows developers to maintain their documentation in a personal knowledge management tool while publishing high-quality, linked web documentation with minimal friction.
The system is built on a modular, pipeline-oriented architecture. It uses a custom `VaultStorage` to index files, a `VaultResolver` for path and name-based lookups, and a specific suite of Remark plugins to handle the Obsidian dialect. This decoupling ensures that the core documentation engine remains agnostic of the input source, while the vault-specific logic resides within isolated plugins that translate complex Obsidian markdown into standard MDX elements like custom components or standardized HTML structures.
## Core Data Structures and Storage Model
The storage model is anchored by `buildStorage`, which ingests raw files and classifies them into one of three formats: `content`, `media`, or `data`. This classification drives how the downstream conversion pipeline treats each entry: `content` files undergo Remark processing and frontmatter parsing, `media` files are treated as assets, and `data` files are preserved as-is.
| Field | Type | Purpose |
| :--- | :--- | :--- |
| `format` | `'content' \| 'media' \| 'data'` | Discriminator for processing logic. |
| `path` | `string` | The original normalized path within the vault. |
| `outPath` | `string` | The target path relative to the output directory. |
| `frontmatter` | `Frontmatter` | Parsed YAML metadata for content files. |
Sources: [packages/obsidian/src/build-storage.ts:39-68](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/build-storage.ts#L39-L68)
## Vault Resolver Mechanism
The `VaultResolver` provides the lookup service for cross-linking. It creates internal mapping tables (`pathToFile`, `nameToFile`) to facilitate lookups by file name, relative path, or absolute vault path.
When `resolveAny(name, fromPath)` is called, the system evaluates the target name:
1. It checks if the string starts with `./` or `../` to resolve relative to the current file's directory.
2. It attempts a lookup against `pathToFile`.
3. It falls back to `nameToFile` if the initial resolution fails, providing robust handling for links that omit directory depth.
```typescript
// Example usage of VaultResolver
const resolver = buildResolver(storage);
const target = resolver.resolveAny('my-note', '/path/to/current/note.md');
```
Sources: [packages/obsidian/src/build-resolver.ts:5-26](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/build-resolver.ts#L5-L26), [packages/obsidian/src/build-resolver.ts:58-69](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/build-resolver.ts#L58-L69)
## Wikilink Transformation Flow
The Wikilink plugin (`remarkWikilinks`) is responsible for converting Obsidian's `[[link]]` syntax into valid Markdown links or embedded components. The process follows a specific order of operations:
1. **Visit:** It traverses the AST to identify `paragraph` nodes containing `[[wikilinks]]`.
2. **Text Parsing:** The `resolveParagraphText` function executes a regex match for the `[[...]]` pattern.
3. **Dispatch:**
- If it is an internal link (`isEmbed = false`), it calls `resolver.resolveAny` and generates a standard markdown `link` node.
- If it is an embed (`isEmbed = true`), it attempts to resolve the file and returns a `mdxJsxFlowElement` (typically an `` component) for content or an `image` node for media assets.
> [!NOTE]
> Wikilinks that resolve to heading-only targets are handled specifically by `getHeadingHash`, which processes the hash segment without slugifying block IDs (strings starting with `^`).
Sources: [packages/obsidian/src/remark/remark-wikilinks.ts:15-17](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/remark/remark-wikilinks.ts#L15-L17), [packages/obsidian/src/remark/remark-wikilinks.ts:84-137](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/remark/remark-wikilinks.ts#L84-L137)
## Callout and Syntax Normalization
Obsidian's blockquote syntax `[!type]` is converted into standard Fumadocs-compatible callout components. The `remarkConvert` plugin handles this:
1. It looks for `blockquote` nodes in the MDAST.
2. `resolveCallout` extracts the type (e.g., `info`, `warning`) from the first line using `RegexCalloutHead`.
3. It uses `mdast-separate` to split the title from the rest of the node content.
4. It calls `createCallout` to transform the structure into a clean JSX/component-ready tree.
Sources: [packages/obsidian/src/remark/remark-convert.ts:10-46](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/remark/remark-convert.ts#L10-L46)
## Block ID Transformation
Obsidian uses `^block-id` as an anchor. The `remarkBlockId` plugin parses these IDs by visiting `paragraph` nodes, scanning for the regex `/(?\w+)$/m`, and replacing the paragraph with a `` element featuring the corresponding `id` attribute.
```mermaid
flowchart TD
A[Visit Paragraph] --> B{Matches Block ID Regex?}
B -->|Yes| C[Extract ID]
C --> D[Replace Node with Section Element]
B -->|No| E[Skip Node]
```
Sources: [packages/obsidian/src/remark/remark-block-id.ts:7-50](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/remark/remark-block-id.ts#L7-L50)
## Lifecycle of a Vault Conversion
The `fromVault` entry point orchestrates the lifecycle:
1. `readVaultFiles`: Fetches raw file contents from the disk.
2. `convertVaultFiles`:
- Builds storage and resolver context.
- Runs the Remark processor chain (`remark-parse` → `remark-gfm` → `remark-math` → `remarkWikilinks` → `remarkConvert` → `remarkObsidianComment` → `remarkBlockId`).
- Stringifies back to MDX using `remark-mdx` and `remark-stringify`.
3. `writeVaultFiles`: Maps the resulting output types (asset, content, data) to their respective destinations.
```mermaid
sequenceDiagram
participant Input as ReadFiles
participant Storage as BuildStorage
participant Proc as Remark Processor
participant Output as WriteFiles
Input->>Storage: Load files
Storage->>Proc: Initialize storage & resolver
Proc->>Proc: Run Remark plugins chain
Proc->>Output: Emit output files
```
Sources: [packages/obsidian/src/index.ts:15-20](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/index.ts#L15-L20), [packages/obsidian/src/convert.ts:57-119](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/convert.ts#L57-L119)
## Related
- [[Content Storage]]
---
## I18n Routing
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/i18n-routing
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/core/src/i18n/middleware.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/i18n/middleware.ts)
- [packages/core/src/source/page-tree/builder.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/page-tree/builder.ts)
- [packages/core/src/source/loader.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/loader.ts)
- [packages/language/src/zh-tw.ts](https://github.com/blade47/fumadocs/blob/main/packages/language/src/zh-tw.ts)
- [packages/language/src/zh-cn.ts](https://github.com/blade47/fumadocs/blob/main/packages/language/src/zh-cn.ts)
- [packages/radix-ui/src/contexts/i18n.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/contexts/i18n.tsx)
- [packages/base-ui/src/contexts/i18n.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/contexts/i18n.tsx)
- [packages/preview/src/pages/[...slugs].tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/pages/%5B...slugs%5D.tsx)
- [packages/core/src/source/dynamic.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/dynamic.ts)
- [packages/core/src/search/orama/create-server.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama/create-server.ts)
- [packages/core/src/source/llms.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/llms.ts)
- [packages/core/src/i18n/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/i18n/index.ts)
- [packages/base-ui/src/layouts/shared/slots/language-select.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/shared/slots/language-select.tsx)
- [packages/core/src/source/storage/content.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/storage/content.ts)
- [packages/radix-ui/src/layouts/shared/slots/language-select.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/layouts/shared/slots/language-select.tsx)
- [packages/base-ui/src/i18n.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/i18n.tsx)
- [packages/radix-ui/src/i18n.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/i18n.tsx)
- [packages/core/src/search/flexsearch.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/flexsearch.ts)
- [packages/core/src/negotiation/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/negotiation/index.ts)
- [packages/openapi/src/i18n.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/i18n.ts)
- [packages/core/src/framework/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/core/src/framework/index.tsx)
- [packages/preview/src/waku.server.tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/waku.server.tsx)
- [packages/core/src/framework/react-router.tsx](https://github.com/blade47/fumadocs/blob/main/packages/core/src/framework/react-router.tsx)
- [packages/core/package.json](https://github.com/blade47/fumadocs/blob/main/packages/core/package.json)
- [packages/base-ui/src/provider/base.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/provider/base.tsx)
- [packages/api-docs/src/i18n.ts](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/i18n.ts)
- [packages/core/src/search/orama-cloud-legacy.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama-cloud-legacy.ts)
- [packages/asyncapi/src/i18n.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/i18n.ts)
- [packages/story/src/i18n.ts](https://github.com/blade47/fumadocs/blob/main/packages/story/src/i18n.ts)
- [packages/radix-ui/src/provider/base.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/provider/base.tsx)
I18n Routing provides the infrastructure for multi-language support across the documentation system. It handles the critical tasks of detecting user locale, managing URL prefixes, and ensuring that page trees and content are correctly scoped to the active language. By integrating with Next.js middleware and specialized loaders, it ensures that users are consistently directed to the correct content translation.
The architecture centers on the `I18nConfig` definition and a dedicated middleware. The system uses a negotiator to determine the user's preferred language, which is then reconciled against the configured `languages` array. This process determines whether a URL rewrite (to keep the locale hidden) or a redirect (to establish the explicit locale in the URL) is necessary, effectively abstracting the complexity of localized pathing away from the UI components.
Beyond routing, the system bridges the gap between language configuration and UI components via context providers. These providers (e.g., `I18nProvider`) manage state such as the current locale and available translations, enabling language switchers and localized text rendering. The system is designed to scale across different packages, allowing UI layers to register translations that are then merged and injected into the React tree.
## Middleware Mechanism
The `createI18nMiddleware` function is the gatekeeper for localized requests. It processes incoming URLs using a `URLFormatter` to identify potential locale codes in the pathname. If no locale is detected or the detected locale is invalid, it negotiates the preferred language based on `request.headers` using `@formatjs/intl-localematcher`.
The middleware implements three `hideLocale` strategies:
- `'never'`: The locale is always visible in the URL path.
- `'default-locale'`: The locale prefix is stripped for the default language but visible for others.
- `'always'`: The locale prefix is hidden entirely; the locale is tracked via a cookie (default: `FD_LOCALE`).
Sources: [packages/core/src/i18n/middleware.ts:56-110](https://github.com/blade47/fumadocs/blob/main/packages/core/src/i18n/middleware.ts#L56-L110)
> [!NOTE]
> When `hideLocale` is set to `always`, the middleware performs a `NextResponse.rewrite` to the locale-prefixed URL if the user doesn't have a locale preference, otherwise it uses the locale found in the cookie.
Sources: [packages/core/src/i18n/middleware.ts:89-93](https://github.com/blade47/fumadocs/blob/main/packages/core/src/i18n/middleware.ts#L89-L93)
## Storage and Locale Parsing
The `createContentStorageBuilder` utility organizes source content by locale. It utilizes a configurable parser to map file system paths to internal virtual paths.
| Parser | Strategy | Path Example |
| :--- | :--- | :--- |
| `dir` | Prefixes the path with a locale directory | `en/docs/page.mdx` → `docs/page.mdx` |
| `dot` | Uses dot notation in the filename | `page.en.mdx` → `page.mdx` |
Sources: [packages/core/src/source/storage/content.ts:54-87](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/storage/content.ts#L54-L87)
The builder scan process separates files into locale-specific maps. If a `fallbackLanguage` is defined, the storage builder performs a chained lookup, inheriting files from the fallback storage when a missing translation is requested.
Sources: [packages/core/src/source/storage/content.ts:153-167](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/storage/content.ts#L153-L167)
## Page Tree Building
The `PageTreeBuilder` creates the hierarchical structure used by sidebars. When initialized with locale support (as an array of `[locale, storages]`), it creates a `PageTreeBuilderContext` specific to that locale. This ensures that the tree built for `/zh-TW` correctly references only the content files assigned to that locale.
```mermaid
flowchart TD
A["createPageTreeBuilder"] --> B["Build Context (Storage + Locale)"]
B --> C["Scan Storage Files"]
C --> D["buildFolder"]
D --> E["Apply Transformers (e.g. Fallback)"]
E --> F["Return PageTree.Root"]
```
Sources: [packages/core/src/source/page-tree/builder.ts:89-136](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/page-tree/builder.ts#L89-L136)
> [!TIP]
> The builder relies on the `own()` function to prevent duplicate node registration when multiple folders reference the same file content; it uses a `priority` check to decide which folder "claims" the node.
Sources: [packages/core/src/source/page-tree/builder.ts:158-184](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/page-tree/builder.ts#L158-L184)
## Translation Management
Translations are defined using `defineI18n` and can be extended with specific keys via `TranslationExtension`. The API supports multiple language presets.
```typescript
import { defineI18n } from 'fumadocs-core/i18n';
import { zhTW } from 'packages/language/src/zh-tw';
const i18n = defineI18n({
languages: ['en', 'zh-TW'],
defaultLanguage: 'en',
});
const t = i18n.translations().preset('zh-TW', zhTW());
```
Sources: [packages/core/src/i18n/index.ts:120-165](https://github.com/blade47/fumadocs/blob/main/packages/core/src/i18n/index.ts#L120-L165)
## UI Context Provider
The `I18nProvider` in both `base-ui` and `radix-ui` shares a common mechanism. It consumes the current locale and a change handler, providing these via React context. The `onChange` implementation computes the new path by replacing the existing locale segment in the URL or prepending a new one if necessary, ensuring the user stays on the same page while switching languages.
Sources: [packages/base-ui/src/contexts/i18n.tsx:42-55](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/contexts/i18n.tsx#L42-L55)
## Search Localization
The search subsystem supports locale-specific indexing. `createI18nSearchAPI` initializes separate Orama search servers for each locale, allowing queries to be routed to the correct language index.
```mermaid
sequenceDiagram
participant C as Client
participant API as SearchAPI
participant S as Server Map
C->>API: search(query, {locale: 'zh-TW'})
API->>S: get('zh-TW')
S-->>API: returns handler
API->>API: execute localized search
```
Sources: [packages/core/src/search/orama/create-server.ts:190-237](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama/create-server.ts#L190-L237)
## Related
- [[Layout System]]
---
## Search Indexing
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/search-indexing
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/create-app/src/plugins/orama-cloud.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/plugins/orama-cloud.ts)
- [packages/mdx/src/plugins/index-file.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/plugins/index-file.ts)
- [packages/core/src/search/orama/create-server.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama/create-server.ts)
- [packages/core/src/mdx-plugins/remark-structure.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-structure.ts)
- [packages/core/src/search/server/build-index.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/server/build-index.ts)
- [packages/core/src/search/client/orama-static.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/orama-static.ts)
- [packages/openapi/src/generate-file.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/generate-file.ts)
- [packages/core/src/search/algolia.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/algolia.ts)
- [packages/core/src/search/flexsearch/utils.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/flexsearch/utils.ts)
- [packages/core/src/search/flexsearch.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/flexsearch.ts)
- [packages/asyncapi/src/generate-file.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/generate-file.ts)
- [packages/openapi/src/utils/pages/builder.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/pages/builder.ts)
- [packages/core/src/source/loader.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/loader.ts)
- [packages/content/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/content/src/index.ts)
- [packages/core/src/search/client/flexsearch-static.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/flexsearch-static.ts)
- [packages/core/src/source/llms.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/llms.ts)
- [packages/core/src/search/server/build-doc.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/server/build-doc.ts)
- [packages/preview/src/pages/_api/api/search.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/pages/_api/api/search.ts)
- [packages/core/package.json](https://github.com/blade47/fumadocs/blob/main/packages/core/package.json)
- [packages/core/src/search/client/orama-cloud-legacy.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/orama-cloud-legacy.ts)
- [packages/mdx/src/runtime/server.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/server.ts)
- [packages/core/src/search/client/orama-cloud.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/orama-cloud.ts)
- [packages/create-app/template/+orama-cloud/@app/lib/export-static-indexes.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/template/%2Borama-cloud/%40app/lib/export-static-indexes.ts)
- [packages/core/src/source/storage/content.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/storage/content.ts)
- [packages/openapi/src/utils/pages/to-static-data.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/pages/to-static-data.ts)
- [packages/core/src/search/orama/create-db.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama/create-db.ts)
- [packages/core/src/search/orama-cloud-legacy.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama-cloud-legacy.ts)
- [packages/sanity/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/sanity/src/index.ts)
- [packages/core/src/search/orama-cloud.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama-cloud.ts)
- [packages/content-collections/src/configuration.ts](https://github.com/blade47/fumadocs/blob/main/packages/content-collections/src/configuration.ts)
Search Indexing is the engine that enables performant full-text search within documentation sites. By transforming raw content—typically Markdown or MDX—into a structured, searchable format, the indexing system bridges the gap between static documents and dynamic user queries. It solves the performance bottleneck of scanning large text bodies in real-time by generating searchable indices (whether static, in-memory, or hosted by third-party services) during the build process.
The system is architected as a modular pipeline: a "content extractor" (such as `remark-structure`) traverses the AST to collect headings and paragraphs, which are then passed to a "builder" component that converts these into documents compatible with specific search backends (Orama, FlexSearch, or Algolia). This decoupling ensures that documentation authors can swap indexing strategies or backends without rewriting their content source logic.
Crucially, the indexing lifecycle interacts closely with the core `loader` system, which manages the virtual file system and page hierarchy. By centralizing index building, the system guarantees that metadata like breadcrumbs, unique page IDs, and language-specific translations remain consistent across both the rendering layer and the search interface.
## Content Extraction with `remark-structure`
The `remark-structure` plugin acts as the primary content extractor. During the MDX build process, it transforms raw files into `StructuredData`, a data model consisting of `headings` and `contents` arrays.
When `remarkStructure` traverses the MDX tree, it uses a configurable `stringify` function to pull text out of elements. It relies on a `lastHeading` pointer to associate paragraphs with the closest preceding header, creating a flat structure that links text content to its specific context.
> [!TIP]
> Always use `remark-heading` before `remark-structure`. If `remark-structure` finds a heading missing an `hProperties.id`, it will skip that heading, preventing it from being added to the `StructuredData` output.
Sources: [packages/core/src/mdx-plugins/remark-structure.ts:134-199](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-structure.ts#L134-L199)
## Document Building Pipeline
Once data is extracted, it must be normalized into a backend-agnostic document format. The `buildDocuments` function performs this transformation. It takes a list of `SharedIndex` objects—each containing metadata like titles, URLs, and structured data—and produces an array of `SharedDocument` entities.
The indexing logic iterates through headings and contents, assigning each a unique ID using a counter and creating granular search entries. The `nextId` counter ensures that every individual paragraph or heading has a distinct anchor.
Sources: [packages/core/src/search/server/build-doc.ts:13-68](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/server/build-doc.ts#L13-L68)
## Backend Strategy: Orama
Orama is an in-memory search engine supported natively by the system. The `createDBSimple` and `createDB` (advanced) functions act as the bridge to Orama's API.
For simple search, `createDBSimple` maps the `SharedIndex` into an object matching `simpleSchema` and calls `insertMultiple` to populate the Orama DB.
```mermaid
flowchart TD
A["Loader Output (Pages)"] --> B["buildIndexDefault (Transform)"]
B --> C["createDBSimple / createDB"]
C --> D["insertMultiple (Orama DB)"]
D --> E["Search API (Ready)"]
```
Sources: [packages/core/src/search/orama/create-db.ts:54-82](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama/create-db.ts#L54-L82)
## Backend Strategy: FlexSearch
FlexSearch, an alternative in-memory backend, is managed through `flexsearch.ts`. It wraps the library's `Document` constructor and exposes an `export` method that serializes the state to a JSON-compatible object.
The system uses a `createDocument` factory that enforces a specific schema, requiring an `id`, `content` field for indexing, and a `tags` array for filtering. Unlike Orama, FlexSearch uses `index.add` inside an `initIndex` routine.
Sources: [packages/core/src/search/flexsearch/utils.ts:70-82](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/flexsearch/utils.ts#L70-L82)
## Integration with Orama Cloud
For larger, production-scale deployments, the system integrates with Orama Cloud via `packages/core/src/search/orama-cloud.ts`. This involves a push-based model where local indexes are transformed into `OramaDocument` types and synced using a `transaction` API.
> [!IMPORTANT]
> The sync process uses a transaction: `index.transaction.open()` starts it, `insertDocuments` adds the batch, and `index.transaction.commit()` finalizes the update. If `autoDeploy` is true, the snapshot is deployed to the production index automatically.
Sources: [packages/core/src/search/orama-cloud.ts:89-99](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama-cloud.ts#L89-L99)
## Client-Side Consumption
The client-side search functionality depends on the backend chosen. For static clients (`oramaStaticClient` or `flexsearchStaticClient`), the client fetches a serialized index file.
1. The client performs a `fetch` request to an API endpoint (e.g., `/api/search`).
2. The index database is reconstructed using `load` (for Orama) or `import` (for FlexSearch).
3. The resulting database instance is cached in a `Map` to prevent redundant fetches.
Sources: [packages/core/src/search/client/orama-static.ts:45-82](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/orama-static.ts#L45-L82)
## Algolia Integration
Algolia support is handled through a `sync` function in `packages/core/src/search/algolia.ts`. It performs an index replacement: it sets index settings (like searchable attributes and faceting) and then calls `client.replaceAllObjects` to ensure the cloud index perfectly matches the local source state.
Sources: [packages/core/src/search/algolia.ts:49-53](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/algolia.ts#L49-L53)
## Related
- [[Orama Integration]]
- [[Search Clients]]
---
## Orama Integration
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/orama-integration
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/core/src/search/client.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client.ts)
- [packages/create-app/src/plugins/orama-cloud.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/plugins/orama-cloud.ts)
- [packages/core/src/search/orama/create-server.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama/create-server.ts)
- [packages/core/src/search/client/orama-static.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/orama-static.ts)
- [packages/core/package.json](https://github.com/blade47/fumadocs/blob/main/packages/core/package.json)
- [packages/core/src/search/client/orama-cloud.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/orama-cloud.ts)
- [packages/base-ui/src/components/dialog/search-orama.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/dialog/search-orama.tsx)
- [packages/radix-ui/src/components/dialog/search-orama.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/dialog/search-orama.tsx)
- [packages/core/src/search/client/orama-cloud-legacy.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/orama-cloud-legacy.ts)
- [packages/openapi/src/ui/operation/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/index.tsx)
- [packages/openapi/src/server/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/server/index.tsx)
- [packages/create-app/template/+orama-cloud/@app/components/search.tsx](https://github.com/blade47/fumadocs/blob/main/packages/create-app/template/%2Borama-cloud/%40app/components/search.tsx)
- [packages/core/src/search/orama/create-db.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama/create-db.ts)
- [packages/core/src/search/client/algolia.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/algolia.ts)
- [packages/core/src/search/orama/search/simple.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama/search/simple.ts)
- [packages/core/src/search/orama-cloud.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama-cloud.ts)
- [packages/api-docs/src/components/schema/client.tsx](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/components/schema/client.tsx)
- [packages/core/src/search/orama-cloud-legacy.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama-cloud-legacy.ts)
- [packages/core/src/search/orama/search/advanced.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama/search/advanced.ts)
- [packages/core/src/search/flexsearch/utils.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/flexsearch/utils.ts)
- [packages/core/src/search/algolia.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/algolia.ts)
- [packages/core/src/search/server/build-index.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/server/build-index.ts)
- [packages/core/src/search/flexsearch.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/flexsearch.ts)
- [packages/create-app/template/+orama-cloud/@app/lib/export-static-indexes.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/template/%2Borama-cloud/%40app/lib/export-static-indexes.ts)
- [packages/core/src/search/mixedbread.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/mixedbread.ts)
- [packages/core/src/search/server.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/server.ts)
- [packages/core/src/search/client/flexsearch-static.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/flexsearch-static.ts)
- [packages/preview/src/pages/_api/api/search.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/pages/_api/api/search.ts)
- [packages/core/src/search/server/endpoint.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/server/endpoint.ts)
- [packages/core/src/search/client/mixedbread.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/mixedbread.ts)
Orama Integration within the Fumadocs ecosystem provides a sophisticated, type-safe approach to full-text search. By leveraging Orama's high-performance search engine (both via Orama Cloud or static local databases), it enables developers to index documentation content dynamically. This integration solves the problem of keeping searchable content in sync with evolving documentation by providing utilities for both server-side indexing and client-side querying.
The system is designed around a modular architecture that separates document indexing logic from the querying interface. It treats documentation content as structured data, which is processed and serialized into searchable schemas (either simple or advanced/vector-capable). This abstraction ensures that search UI components—like the `OramaSearchDialog`—can remain decoupled from the underlying data source implementation while maintaining a unified developer experience.
By utilizing this integration, developers can leverage Orama's advanced capabilities, including vector search and faceted filtering, while maintaining compatibility with internationalization (i18n) and large-scale site structures. The integration ensures performance by providing mechanisms for debounced searching and cached database loading, minimizing browser overhead while maximizing retrieval accuracy.
## Search Server Initialization
The search server acts as the primary orchestrator for indexing documentation pages. It leverages `createSearchAPI` to define the search strategy, specifically supporting `'simple'` and `'advanced'` modes. The initialization process transforms raw source data (typically from the loader) into an Orama index.
The mechanism follows a pattern where content is processed into defined schemas. For `'advanced'` search, the schema includes `embeddings` (vector), `page_id`, and `tags`, enabling complex queries. The `createDB` and `createDBSimple` functions manage the ingestion of structured data into Orama's internal instances.
```mermaid
flowchart TD
A["Loader / Source"] --> B["buildIndexDefault()"]
B --> C["createSearchAPI()"]
C --> D{"Type"}
D -->|"simple"| E["initSimpleSearch()"]
D -->|"advanced"| F["initAdvancedSearch()"]
E --> G["createDBSimple()"]
F --> H["createDB()"]
G --> I["insertMultiple()"]
H --> I
```
Sources: [packages/core/src/search/orama/create-server.ts:136-149](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama/create-server.ts#L136-L149), [packages/core/src/search/orama/create-db.ts:32-82](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama/create-db.ts#L32-L82)
## Orama Cloud Indexing Pipeline
When utilizing Orama Cloud, the system syncs local data to the cloud via a dedicated script. This pipeline is managed by a Fumadocs template plugin that generates a `sync-content.ts` script. This script fetches pre-rendered static indexes (serialized as `OramaDocument` objects) and pushes them to the Orama Cloud project via the Orama Cloud SDK.
The data transformation step, `toIndex`, is critical. It decomposes a single `OramaDocument` (representing a page) into multiple `OramaIndex` items: one for each section heading and one for the page description. This expansion allows the search engine to return precise document sections rather than just entire pages.
```mermaid
sequenceDiagram
participant Build as Build System
participant Script as sync-content.ts
participant Cloud as Orama Cloud
Build->>Script: Execute sync
Script->>Script: Parse static.json
Script->>Script: Perform toIndex() expansion
Script->>Cloud: transaction.insertDocuments()
Script->>Cloud: transaction.commit()
```
Sources: [packages/create-app/src/plugins/orama-cloud.ts:77-104](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/plugins/orama-cloud.ts#L77-L104), [packages/core/src/search/orama-cloud.ts:118-159](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama-cloud.ts#L118-L159)
> [!NOTE]
> When using `orama-cloud`, the search service is externalized. Ensure `NEXT_PUBLIC_ORAMA_PROJECT_ID` and `ORAMA_PRIVATE_API_KEY` are defined in the build environment to enable the synchronization task to authenticate successfully.
## Client-Side Search Interface
The `useDocsSearch` hook provides a unified API for interacting with various search backends, including `oramaStaticClient` and `oramaCloudClient`. It manages state (`search`, `isLoading`, `data`, `error`) and handles debouncing of input values to optimize search performance.
The hook operates using a `SearchClient` interface. When `oramaCloudClient` is invoked, it returns a search implementation that delegates query execution to the Orama Cloud SDK. If `index` is set to `'crawler'`, it performs a raw search against Orama Cloud's hits; otherwise, it handles group aggregation (`groupBy`) to ensure search results are presented hierarchically (pages with embedded sections).
Sources: [packages/core/src/search/client.ts:83-197](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client.ts#L83-L197), [packages/core/src/search/client/orama-cloud.ts:40-134](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/orama-cloud.ts#L40-L134)
## Advanced Search Logic and Sorting
Advanced search operations, specifically `searchAdvanced`, utilize Orama's `groupBy` feature to aggregate related search hits under a specific `page_id`. This prevents the search dialog from becoming cluttered with duplicate page entries if multiple sections of the same page match the query term.
The selection logic is implemented as follows:
1. Results are searched using Orama's full-text search.
2. If `groupBy` is defined, hits are clustered.
3. The results are iterated, and for every `group` found, the `page_id` is used to fetch the document representing the parent page.
4. The page title is pushed first, followed by sections.
This ensures a predictable and stable rendering order for the UI components.
Sources: [packages/core/src/search/orama/search/advanced.ts:6-77](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama/search/advanced.ts#L6-L77)
## Configuration and Options
The Orama integration offers several configuration interfaces, primarily distinguished by the search mode (Simple vs. Advanced).
| Option | Type | Description |
| :--- | :--- | :--- |
| `indexes` | `Array` | The list of data documents to index. |
| `language` | `string` | The stemmer language to use for tokenization. |
| `tokenizer` | `object` | Custom tokenization strategy if the default is insufficient. |
| `tag` | `string` | Filter for search results in the UI. |
| `mode` | `'full'\|'vector'` | Enables vector search (requires `@orama/plugin-embeddings`). |
Sources: [packages/core/src/search/orama/create-server.ts:39-60](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama/create-server.ts#L39-L60), [packages/core/src/search/orama/create-server.ts:151-163](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama/create-server.ts#L151-L163)
> [!WARNING]
> Enabling `mode: 'vector'` requires installing `@orama/plugin-embeddings` separately. Failure to install this plugin will result in a runtime error during search execution because the internal vector search engine will be missing.
## Lifecycle and Caching
The integration employs aggressive caching to maintain performance. Specifically, `oramaStaticClient` uses a `cache` map indexed by the `from` (URL) property to ensure that the database is loaded only once per session. The `createFromSource` function in the server uses a `WeakMap` to associate `LoaderOutput` instances with their respective `SearchServer` instances.
This `WeakMap` implementation acts as a memory safety layer, allowing the garbage collector to reclaim server instances if the underlying `LoaderOutput` is discarded, effectively preventing memory leaks during hot-reloads or dynamic documentation updates.
Sources: [packages/core/src/search/client/orama-static.ts:34-93](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/orama-static.ts#L34-L93), [packages/core/src/search/orama/create-server.ts:278-314](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama/create-server.ts#L278-L314)
## Related
- [[Search Indexing]]
---
## Search Clients
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/search-clients
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/core/src/search/client.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client.ts)
- [packages/create-app/src/plugins/orama-cloud.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/plugins/orama-cloud.ts)
- [packages/core/src/search/orama/create-server.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama/create-server.ts)
- [packages/core/src/search/client/orama-static.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/orama-static.ts)
- [packages/core/src/search/client/algolia.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/algolia.ts)
- [packages/radix-ui/src/components/dialog/search.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/dialog/search.tsx)
- [packages/core/src/search/client/orama-cloud.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/orama-cloud.ts)
- [packages/core/src/search/client/orama-cloud-legacy.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/orama-cloud-legacy.ts)
- [packages/base-ui/src/components/dialog/search-orama.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/dialog/search-orama.tsx)
- [packages/radix-ui/src/components/dialog/search-orama.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/dialog/search-orama.tsx)
- [packages/core/src/search/client/flexsearch-static.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/flexsearch-static.ts)
- [packages/radix-ui/src/components/dialog/search-algolia.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/dialog/search-algolia.tsx)
- [packages/core/src/search/client/mixedbread.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/mixedbread.ts)
- [packages/base-ui/src/components/dialog/search-algolia.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/dialog/search-algolia.tsx)
- [packages/core/src/search/flexsearch/utils.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/flexsearch/utils.ts)
- [packages/core/src/search/algolia.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/algolia.ts)
- [packages/core/src/search/flexsearch.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/flexsearch.ts)
- [packages/base-ui/src/components/dialog/search-default.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/dialog/search-default.tsx)
- [packages/core/src/search/client/fetch.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/fetch.ts)
- [packages/create-app/template/+orama-cloud/@app/components/search.tsx](https://github.com/blade47/fumadocs/blob/main/packages/create-app/template/%2Borama-cloud/%40app/components/search.tsx)
- [packages/core/src/search/mixedbread.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/mixedbread.ts)
- [packages/base-ui/src/contexts/search.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/contexts/search.tsx)
- [packages/radix-ui/src/contexts/search.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/contexts/search.tsx)
- [packages/mdx/src/runtime/browser.tsx](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/browser.tsx)
- [packages/preview/src/pages/_api/api/search.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/pages/_api/api/search.ts)
- [packages/core/src/search/orama/create-db.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama/create-db.ts)
- [packages/core/src/search/orama-cloud.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama-cloud.ts)
- [packages/core/src/search/orama-cloud-legacy.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama-cloud-legacy.ts)
- [packages/core/src/search/orama/search/simple.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama/search/simple.ts)
- [packages/core/src/search/orama/search/advanced.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/orama/search/advanced.ts)
Search Clients in the Fumadocs ecosystem provide a unified interface for document search across diverse backends. They bridge the gap between UI components and search indexes, ensuring that users experience consistent behavior regardless of whether the search is performed against a local index (static/in-memory), a remote API, or a managed search provider.
By abstracting search logic behind a standard `SearchClient` interface, this subsystem allows developers to toggle between providers without refactoring UI components. This is achieved by utilizing the `useDocsSearch` hook, which manages loading states, result aggregation, and debouncing, enabling a seamless integration into documentation dialogs.
The design relies on a separation of concerns where the search engine handles indexing and querying, while the client implementation handles communication and result formatting. This architecture ensures that as indexing strategies evolve, the front-end remains decoupled from the low-level search implementation details.
## The `useDocsSearch` Orchestration Hook
The `useDocsSearch` hook is the primary entry point for managing search state in the browser. It implements a reactive workflow that responds to query updates and provider changes.
Mechanistically, it maintains four internal states: `search` (the current string), `results` (the `SortedResult` data), `error`, and `isLoading`. It utilizes `useDebounce` to throttle input updates, preventing excessive calls to remote APIs.
Crucially, it manages task cancellation using a `ref` object (`activeTaskRef`). When dependencies (like the client configuration or debounced query) change, the hook interrupts any pending execution to prevent race conditions. The core execution logic:
1. Checks if the new query state is empty (respecting `allowEmpty` flag).
2. Triggers the search via the provided `client.search()` method.
3. Guards the update cycle: if `activeTaskRef.current.interrupt` is true, it skips result updates (`178|`, `184|`, `186|`).
Sources: [packages/core/src/search/client.ts:83-197](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client.ts#L83-L197)
## Search Provider Implementations
Each search client implements the `SearchClient` interface, which defines a mandatory `search` method returning `Awaitable`.
| Client | Implementation Strategy | Dependencies |
| :--- | :--- | :--- |
| `fetch` | HTTP GET via `fetch()` | API URL, locale, tag |
| `orama-static` | Loads remote JSON blob into memory | URL (remote), locale, tag |
| `algolia` | `algoliasearch` SDK | indexName, client, tag |
| `orama-cloud` | `@orama/core` Cloud SDK | OramaCloud instance |
| `flexsearch-static` | Loads FlexSearch blob into memory | API URL, locale, tag |
Sources: [packages/core/src/search/client.ts:73-76](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client.ts#L73-L76), [packages/core/src/search/client/fetch.ts:27-52](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/fetch.ts#L27-L52), [packages/core/src/search/client/orama-static.ts:95-118](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/orama-static.ts#L95-L118), [packages/core/src/search/client/algolia.ts:54-89](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/algolia.ts#L54-L89), [packages/core/src/search/client/flexsearch-static.ts:23-41](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/flexsearch-static.ts#L23-L41)
## Static Index Lifecycle: Orama & FlexSearch
Static clients download search indexes once and cache them in memory. This improves search speed by eliminating round-trips for subsequent queries.
For `orama-static`, the mechanism is:
1. `getDBCached` checks a module-level `cache` Map keyed by the index URL (`86|`).
2. If missing, `loadDB` fetches the index, initializes an Orama instance using `create`, and populates it using `load` (`74|`).
3. The search handler then retrieves the DB from the cache and delegates to the appropriate search utility (`101|`, `112|`).
> [!WARNING]
> The search index database is cached in a module-level Map variable `cache`. If the application dynamically changes the `from` URL without full page reloads, the client will fail to update its local index until a full refresh occurs.
Sources: [packages/core/src/search/client/orama-static.ts:34-93](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/orama-static.ts#L34-L93)
## Search Dialog Integration
The UI components (`SearchDialog`) interact with the search clients via common Context providers. For example, `OramaSearchDialog` wraps the core dialog and injects a client generated by `oramaCloudClient`.
The interaction flow is:
1. The user types into `SearchDialogInput`.
2. `onSearchChange` updates the state in the hook provided by the parent `SearchProvider`.
3. `useDocsSearch` reacts to the state change, triggers the `SearchClient.search` method, and updates the `data` result.
4. `SearchDialogList` renders the result array provided by the `useSearchList` hook.
Sources: [packages/base-ui/src/components/dialog/search-orama.tsx:68-76](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/dialog/search-orama.tsx#L68-L76), [packages/radix-ui/src/components/dialog/search.tsx:299-403](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/dialog/search.tsx#L299-L403)
## Result Aggregation & Grouping
When multiple hits correspond to a single documentation page, clients must perform grouping to avoid redundant list items. In the `algoliaClient`, this is handled by `groupResults`:
1. A `Set` named `scannedUrls` tracks unique URLs (`28|`).
2. If a URL is not yet in the set, the hit is added as a 'page' type.
3. All hits (including fragments/sections) are pushed to the list to allow deep linking to sections.
4. This selection logic ensures that every page has a header entry, followed by its child sections.
Sources: [packages/core/src/search/client/algolia.ts:26-52](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/algolia.ts#L26-L52)
## Usage Example
Developers can integrate the search subsystem by creating a dialog component and wrapping it in a provider. Here is a minimal implementation using the default `fetchClient`:
```typescript
// Example: Using the Search Dialog with the Fetch client
import { fetchClient } from 'fumadocs-core/search/client/fetch';
import { useDocsSearch } from 'fumadocs-core/search/client';
function MySearch() {
const { search, setSearch, query } = useDocsSearch({
client: fetchClient({ api: '/api/search' }),
});
return (
);
}
```
Sources: [packages/core/src/search/client/fetch.ts:27-52](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client/fetch.ts#L27-L52), [packages/core/src/search/client.ts:83-197](https://github.com/blade47/fumadocs/blob/main/packages/core/src/search/client.ts#L83-L197)
## Related
- [[Search Indexing]]
- [[Search Dialogs]]
---
## Search Dialogs
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/search-dialogs
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/base-ui/src/components/dialog/search.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/dialog/search.tsx)
- [packages/radix-ui/src/components/dialog/search.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/dialog/search.tsx)
- [packages/api-docs/src/components/schema/client.tsx](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/components/schema/client.tsx)
- [packages/preview/src/components/ai/search.tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/components/ai/search.tsx)
- [packages/base-ui/src/layouts/notebook/slots/sidebar.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/notebook/slots/sidebar.tsx)
- [packages/radix-ui/src/layouts/notebook/slots/sidebar.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/layouts/notebook/slots/sidebar.tsx)
- [packages/base-ui/src/components/dialog/search-default.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/dialog/search-default.tsx)
- [packages/openapi/src/ui/operation/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/index.tsx)
- [packages/radix-ui/src/components/dialog/search-default.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/dialog/search-default.tsx)
- [packages/base-ui/src/components/dialog/search-algolia.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/dialog/search-algolia.tsx)
- [packages/radix-ui/src/components/dialog/search-algolia.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/dialog/search-algolia.tsx)
- [packages/asyncapi/src/ui/components/server-select.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/components/server-select.tsx)
- [packages/base-ui/src/contexts/search.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/contexts/search.tsx)
- [packages/base-ui/src/components/dialog/search-orama.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/dialog/search-orama.tsx)
- [packages/radix-ui/src/contexts/search.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/contexts/search.tsx)
- [packages/radix-ui/src/components/dialog/search-orama.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/dialog/search-orama.tsx)
- [packages/base-ui/src/components/sidebar/base.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/sidebar/base.tsx)
- [packages/base-ui/src/layouts/shared/slots/search-trigger.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/shared/slots/search-trigger.tsx)
- [packages/api-docs/src/components/select-tab.tsx](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/components/select-tab.tsx)
- [packages/radix-ui/src/components/sidebar/base.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/sidebar/base.tsx)
- [packages/create-app/template/+orama-cloud/@app/components/search.tsx](https://github.com/blade47/fumadocs/blob/main/packages/create-app/template/%2Borama-cloud/%40app/components/search.tsx)
- [packages/radix-ui/src/layouts/shared/slots/search-trigger.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/layouts/shared/slots/search-trigger.tsx)
- [packages/radix-ui/src/layouts/shared/page-actions.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/layouts/shared/page-actions.tsx)
Search Dialogs are a core feature of the UI subsystem, providing a centralized interface for users to discover content across documentation sets. By offering a standardized way to invoke and navigate search results, these components ensure a consistent user experience while remaining decoupled from specific search backends. They act as a bridge between high-level application state and low-level search implementation details.
The design relies on a provider-based architecture, allowing the search functionality to be configured and injected throughout the application. This modularity enables developers to swap out search providers—such as the default fetch-based search, Algolia, or Orama—without modifying the consumption layer. By providing a clean API surface for triggers and content, the system facilitates rapid integration into various layouts.
Internally, the Search Dialog subsystem manages complex state, including keyboard navigation, focus trapping, and asynchronous loading states. It leverages React context to share search input and selection logic among diverse components, ensuring that triggers, inputs, and results stay synchronized. This architecture minimizes boilerplate while offering hooks for advanced customizations, such as filtering and metadata display.
## Core Architecture and Contexts
The search system is initialized through a `SearchProvider`, which manages the global search state including the "open" boolean and the configuration of the dialog component.
```mermaid
flowchart TD
SP[SearchProvider] --> Context[SearchContextType]
Context --> Trigger[SearchTrigger]
Context --> Dialog[SearchDialog]
Dialog --> C[RootContext]
C --> Content[SearchDialogContent]
C --> List[SearchDialogList]
```
Sources: [packages/base-ui/src/contexts/search.tsx:112-162](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/contexts/search.tsx#L112-L162), [packages/base-ui/src/components/dialog/search.tsx:56-63](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/dialog/search.tsx#L56-L63)
The `SearchProvider` acts as the single source of truth for whether the search modal should be displayed. It subscribes to global keyboard events (defaulting to Meta+K) to trigger the `setIsOpen` state function, ensuring immediate accessibility.
Sources: [packages/base-ui/src/contexts/search.tsx:121-133](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/contexts/search.tsx#L121-L133)
## Dialog State Management
The `SearchDialog` acts as a controlled component wrapping the Radix `Dialog.Root`. It uses an internal context to distribute the search change and selection handlers down the tree.
> [!TIP]
> The use of `useRef` for callbacks in `SearchDialog` prevents unnecessary re-renders of the entire dialog tree when the parent’s search state updates, optimizing performance for fast-typing users.
Sources: [packages/base-ui/src/components/dialog/search.tsx:177-213](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/dialog/search.tsx#L177-L213)
When an item is selected in the list, the `onSelect` function determines the navigation target. It checks the item type and dispatches to the router if it's a standard page, or executes an `onSelect` function if the item is an action.
```mermaid
sequenceDiagram
participant User
participant List as SearchDialogList
participant Handler as onSelect(item)
User->>List: Press Enter
List->>Handler: Call selected handler
alt item.type == 'action'
Handler->>Handler: item.onSelect()
else item.external
Handler->>Handler: window.open()
else default
Handler->>Handler: router.push(item.url)
end
Handler->>List: Close Dialog
```
Sources: [packages/base-ui/src/components/dialog/search.tsx:181-192](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/dialog/search.tsx#L181-L192)
## Keyboard Navigation
Keyboard navigation is handled within `SearchDialogList`. The component uses `useEffectEvent` to track the `active` state and allows users to traverse results using arrow keys.
> [!WARNING]
> The logic within `SearchDialogList` ensures that list indices loop correctly using the modulo operator: `setActive(items.at(idx % items.length)?.id ?? null)`. This prevents out-of-bounds access.
Sources: [packages/base-ui/src/components/dialog/search.tsx:339-358](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/dialog/search.tsx#L339-L358)
## Search Providers (Algolia, Orama)
The system exposes a standard interface for custom search backends. By wrapping `useDocsSearch` with specific client drivers, it unifies diverse API structures into the `SortedResult` interface.
| Feature | `fetchClient` (Default) | `algoliaClient` | `oramaCloudClient` |
| :--- | :--- | :--- | :--- |
| **Logic** | HTTP fetch | Algolia SDK | Orama API |
| **Config** | API URL | Algolia options | Client/Index config |
Sources: [packages/base-ui/src/components/dialog/search-default.tsx:63-70](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/dialog/search-default.tsx#L63-L70), [packages/base-ui/src/components/dialog/search-algolia.tsx:61-67](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/dialog/search-algolia.tsx#L61-L67), [packages/base-ui/src/components/dialog/search-orama.tsx:68-76](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/dialog/search-orama.tsx#L68-L76)
## Customizing the Search Result Rendering
The `SearchDialogListItem` uses a `renderMarkdown` function to render search results. This renderer is configured with specific components to handle highlighted matches, links, and code blocks within the snippet display.
Sources: [packages/base-ui/src/components/dialog/search.tsx:425-470](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/dialog/search.tsx#L425-L470)
The list item component distinguishes between types (page, heading, text) and styles them accordingly, often applying specific padding and icons to indicate hierarchical structure.
Sources: [packages/base-ui/src/components/dialog/search.tsx:451-468](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/dialog/search.tsx#L451-L468)
## Worked Example: Implementing a Custom Search Dialog
To build a custom dialog, create a component that implements the `SharedProps` and utilizes the exported UI sub-components from the search module.
```typescript
import {
SearchDialog,
SearchDialogHeader,
SearchDialogInput,
SearchDialogList,
type SharedProps
} from 'fumadocs-ui/components/dialog/search';
export default function MyCustomSearch(props: SharedProps) {
const [search, setSearch] = useState('');
// Implementation of query logic here...
return (
);
}
```
Sources: [packages/create-app/template/+orama-cloud/@app/components/search.tsx:4-54](https://github.com/blade47/fumadocs/blob/main/packages/create-app/template/%2Borama-cloud/%40app/components/search.tsx#L4-L54)
## Related
- [[Search Clients]]
- [[Layout System]]
---
## OpenAPI Generation
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/openapi-generation
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/openapi/src/ui/operation/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/index.tsx)
- [packages/openapi/src/types/openapi.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/types/openapi.ts)
- [packages/api-docs/src/components/schema/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/components/schema/index.tsx)
- [packages/api-docs/src/components/schema/client.tsx](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/components/schema/client.tsx)
- [packages/openapi/src/server/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/server/index.tsx)
- [packages/openapi/src/utils/pages/preset-auto.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/pages/preset-auto.ts)
- [packages/api-docs/src/schema/dereference.ts](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/schema/dereference.ts)
- [packages/openapi/src/utils/pages/builder.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/pages/builder.ts)
- [packages/openapi/src/generate-file.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/generate-file.ts)
- [packages/asyncapi/src/generate-file.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/generate-file.ts)
- [packages/openapi/src/utils/document/dereference.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/document/dereference.ts)
- [packages/asyncapi/src/server/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/server/index.tsx)
- [packages/openapi/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/tsdown.config.ts)
- [packages/openapi/src/ui/base.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/base.tsx)
- [packages/openapi/src/utils/pages/to-static-data.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/pages/to-static-data.ts)
- [packages/openapi/src/ui/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/index.tsx)
- [packages/asyncapi/src/ui/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/index.tsx)
- [packages/openapi/src/utils/document/load.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/document/load.ts)
- [packages/asyncapi/src/utils/document/dereference.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/utils/document/dereference.ts)
- [packages/api-docs/src/schema/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/schema/index.ts)
- [packages/openapi/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/index.ts)
- [packages/asyncapi/src/utils/pages/to-static-data.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/utils/pages/to-static-data.ts)
- [packages/openapi/src/types.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/types.ts)
- [packages/openapi/package.json](https://github.com/blade47/fumadocs/blob/main/packages/openapi/package.json)
- [packages/asyncapi/src/utils/pages/builder.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/utils/pages/builder.ts)
- [packages/api-docs/src/schema/bundle.ts](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/schema/bundle.ts)
- [packages/asyncapi/src/utils/schema.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/utils/schema.ts)
- [packages/asyncapi/src/utils/document/load.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/utils/document/load.ts)
- [packages/openapi/src/requests/generators/all.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/requests/generators/all.ts)
- [packages/asyncapi/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/index.ts)
OpenAPI Generation provides a sophisticated pipeline to transform OpenAPI specification documents into interactive, structured documentation pages. At its core, the system acts as a specialized build-time processor and runtime UI framework that bridges the gap between raw API schemas and developer-friendly documentation. By automating the parsing, dereferencing, and file-system generation, it ensures documentation remains tightly coupled with source specifications, eliminating the manual overhead typically associated with API documentation maintenance.
The subsystem follows a multi-stage architecture. Initially, it ingests input documents (URLs, local paths, or objects), validates them, and bundles references using `json-schema-ref-parser`. Once bundled, the system provides both a static generation path for pre-rendered site building and a dynamic runtime path for modern web framework integrations. This design ensures that the same parsing logic, type resolution, and UI component rendering are consistent regardless of whether the docs are built for static hosting or served dynamically.
A significant architectural feature is the clear separation between core specification handling and UI rendering. The generation logic treats the OpenAPI document as a graph of nodes, which are then traversed to build virtual files representing API endpoints or tags. This graph-based approach enables the system to support complex configuration (like automatic grouping by tags or routes) while maintaining a strict, predictable data structure that downstream UI components can safely consume without needing to re-parse the raw specification.
## Core Document Processing and Bundling
The lifecycle begins with the document loader, which handles the transition from raw input to a normalized internal format. Because specifications often contain complex cross-file references, the `loadDocument` function acts as the primary gatekeeper.
The mechanism performs the following sequence:
1. **Bundling:** Uses `bundle(input)` to resolve all external `$ref` pointers into a single coherent document tree.
2. **Upgrading:** Pipes the resulting document through `@scalar/openapi-upgrader` to ensure version compatibility (upgrading to `3.2`), normalizing the schema structure.
3. **Caching:** The `createOpenAPI` server uses a `Map` to cache these promises, preventing redundant I/O operations if the system re-requests the same schema identifier during the build.
> [!IMPORTANT]
> The `loadDocument` utility is the only point where the raw specification enters the system. It guarantees that any document reaching the generation phase is already unified and compliant with the internal version 3.2 schema expectations.
Sources: [packages/openapi/src/utils/document/load.ts:8-20](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/document/load.ts#L8-L20)
## Schema Dereferencing and Inlining
Once a document is loaded, its components must be dereferenced for the UI to represent data structures accurately. The core mechanism resides in the `dereferenceSync` function.
The mechanism follows a recursive traversal:
1. **Cloning:** It first uses `structuredClone` to create a working copy, isolating mutations.
2. **Pointer Traversal:** It walks the schema object graph, maintaining a `visitedNodes` set to prevent infinite cycles.
3. **Inlining:** When it encounters a `$ref` object, it resolves the pointer against the root schema and merges the resolved object into the current location, replacing the `$ref` entirely unless a `preserveRef` check permits keeping the pointer (useful for specific schema viewers).
4. **Reference Mapping:** The `setOriginalRef` callback enables the UI to map resolved nodes back to their original reference paths, which is critical for generating valid "go-to-definition" UI behaviors.
Sources: [packages/api-docs/src/schema/dereference.ts:19-65](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/schema/dereference.ts#L19-L65)
## Virtual File Generation Logic
The `fromSchema` factory function is the heart of the site generation subsystem. It converts an OpenAPI spec into a list of `OutputEntry` objects that are later rendered as MDX files.
The logic proceeds via a `PagesBuilder` object that encapsulates the schema and provides accessors for operations, webhooks, and tags:
1. **Extraction:** The `extract()` method iterates over `paths` and `webhooks`, identifying every valid method (e.g., GET, POST) and creating an `OperationItem` for each.
2. **Grouping:** The `preset-auto` logic, specifically the `group()` function, takes these items and applies grouping logic defined in the `SchemaToPagesOptions`.
3. **Path Mapping:** The `routePathToFilePath` method converts RESTful URL patterns (e.g., `{id}`) into filesystem-friendly paths, ensuring predictable URLs for the resulting documentation pages.
> [!NOTE]
> During grouping, entries are sorted and collected into `OutputGroup` structures before being pushed to the final file manifest. This ensures that parent `meta.json` files are correctly generated to represent the structure of the API documentation tree.
Sources: [packages/openapi/src/utils/pages/builder.ts:121-231](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/pages/builder.ts#L121-L231), [packages/openapi/src/utils/pages/preset-auto.ts:150-260](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/pages/preset-auto.ts#L150-L260)
## UI Component Composition
The visual documentation is rendered by the `Operation` component, which acts as a layout container for specific API details.
The rendering pipeline is highly flexible:
1. **Slotting:** The component defines slots for `header`, `description`, `authSchemes`, `parameters`, `body`, and `responses`.
2. **Injection:** The actual layout is dictated by `renderOperationLayout` provided via `RenderContext`. This pattern allows users to swap the order of UI blocks without modifying the underlying component code.
3. **State Management:** An `OperationProvider` is wrapped around the content to manage state like example ID selection (e.g., `x-exclusiveCodeSample`).
```typescript
// Example: The internal layout slotting
let content = renderOperationLayout(
{
header: headNode,
description: descriptionNode,
authSchemes: authNode,
body: bodyNode,
callbacks: callbacksNode,
parameters: parameterNode,
responses: responseNode,
apiPlayground,
apiExample: ,
},
{ operation, method, pathItem, ctx },
);
```
Sources: [packages/openapi/src/ui/operation/index.tsx:318-389](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/index.tsx#L318-L389)
## Schema Tree Visualization
The `SchemaUI` component generates an interactive tree representation of JSON schemas. It uses a recursive approach to handle object properties, arrays, and union types (`oneOf`, `anyOf`, `allOf`).
Key design decisions in `generateSchemaUI`:
- **Caching:** The function maintains an `autoIds` `WeakMap` to ensure that identical schema objects receive stable, predictable IDs across different rendering passes.
- **Filtering:** The `isVisible` guard handles field-level visibility based on the `readOnly` or `writeOnly` flags, allowing the UI to omit fields that are not relevant to the current request/response context.
| Type | Handling Mechanism | Key Logic Path |
| :--- | :--- | :--- |
| Object | Maps properties to an array of props | `out.props.push({...})` |
| Array | Resolves items schema via `getSchemaId` | `scanRefs($type, items)` |
| Union | Collapses types into a selectable toggle | `out.items.push({...})` |
| Primitive | Renders alias and info tags | `...base(schema)` |
Sources: [packages/api-docs/src/components/schema/index.tsx:114-414](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/components/schema/index.tsx#L114-L414)
## Development and Build Lifecycle
The system utilizes `tsdown` for bundling with an `onSuccess` hook that triggers CSS generation. The CSS system uses a `Scanner` from `@tailwindcss/oxide` to crawl the UI components and extract styles.
> [!CAUTION]
> The `onSuccess` hook is critical for build-time asset generation. Manual changes to the generated CSS files inside `css/generated/` will be overwritten in the next build cycle.
Sources: [packages/openapi/tsdown.config.ts:7-77](https://github.com/blade47/fumadocs/blob/main/packages/openapi/tsdown.config.ts#L7-L77)
## Related
- [[API Schema Processing]]
- [[Code Generation]]
- [[API UI]]
---
## AsyncAPI Generation
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/asyncapi-generation
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/asyncapi/src/types/asyncapi-3.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/types/asyncapi-3.ts)
- [packages/typescript/src/ui/auto-type-table.tsx](https://github.com/blade47/fumadocs/blob/main/packages/typescript/src/ui/auto-type-table.tsx)
- [packages/openapi/src/types/openapi.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/types/openapi.ts)
- [packages/asyncapi/src/server/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/server/index.tsx)
- [packages/openapi/src/server/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/server/index.tsx)
- [packages/asyncapi/src/generate-file.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/generate-file.ts)
- [packages/openapi/src/utils/pages/builder.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/pages/builder.ts)
- [packages/api-docs/src/components/schema/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/components/schema/index.tsx)
- [packages/mdx/src/runtime/server.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/server.ts)
- [packages/asyncapi/src/ui/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/index.tsx)
- [packages/asyncapi/src/utils/document/dereference.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/utils/document/dereference.ts)
- [packages/asyncapi/src/utils/document/load.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/utils/document/load.ts)
- [packages/openapi/src/utils/document/dereference.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/document/dereference.ts)
- [packages/asyncapi/src/utils/schema.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/utils/schema.ts)
- [packages/openapi/src/utils/document/load.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/document/load.ts)
- [packages/openapi/src/utils/pages/to-static-data.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/pages/to-static-data.ts)
- [packages/asyncapi/src/utils/traits.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/utils/traits.ts)
- [packages/content-collections/src/configuration.ts](https://github.com/blade47/fumadocs/blob/main/packages/content-collections/src/configuration.ts)
- [packages/asyncapi/src/utils/pages/to-static-data.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/utils/pages/to-static-data.ts)
- [packages/asyncapi/src/utils/pages/builder.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/utils/pages/builder.ts)
- [packages/asyncapi/src/ui/bindings/protocols/mqtt.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/mqtt.tsx)
- [packages/asyncapi/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/index.ts)
- [packages/python/fumapy/mksource/document_module.py](https://github.com/blade47/fumadocs/blob/main/packages/python/fumapy/mksource/document_module.py)
- [packages/asyncapi/src/ui/bindings/protocols/solace.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/solace.tsx)
- [packages/asyncapi/src/utils/get-example-messages.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/utils/get-example-messages.ts)
- [packages/api-docs/src/schema/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/schema/index.ts)
- [packages/asyncapi/src/ui/bindings/protocols/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/index.ts)
- [packages/doc-gen/src/remark-docgen.ts](https://github.com/blade47/fumadocs/blob/main/packages/doc-gen/src/remark-docgen.ts)
- [packages/asyncapi/src/ui/bindings/protocols/anypointmq.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/anypointmq.tsx)
- [packages/asyncapi/package.json](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/package.json)
"AsyncAPI Generation" is a robust subsystem dedicated to transforming complex AsyncAPI specifications into structured, navigable documentation. By leveraging a modular architecture, it abstracts the complexity of specification parsing and data normalization, enabling developers to integrate event-driven API documentation into their workflows with minimal friction. The subsystem primarily solves the problem of "specification drift"—the gap between technical documentation and implementation—by providing both runtime generation tools and static export capabilities.
Architecturally, the subsystem is built on a "builder" pattern that processes AsyncAPI specifications into a normalized, internal format, which then flows into dedicated layout engines. These engines generate the virtual files required by the broader Fumadocs documentation pipeline. By separating concerns between document loading, schema dereferencing, and output building, it ensures that changes in the specification format are isolated, while UI components can rely on a consistent, unified model for rendering.
The system interacts extensively with lower-level document processing utilities, such as dereferencing tools that resolve `$ref` links and bundling utilities that normalize external schema references. By integrating directly into the `fumadocs-core` ecosystem, it functions as a loader plugin or a build-time generator, facilitating a seamless transition from raw AsyncAPI JSON/YAML files to interactive, high-fidelity documentation pages.
## Document Loading and Bundling
The subsystem begins by transforming raw input (URLs, local file paths, or objects) into a standardized `LoadedDocument`. This process is governed by `loadDocument` in `packages/asyncapi/src/utils/document/load.ts`. The central utility, `bundle`, is used to normalize the schema, ensuring that all components are accounted for, and a check is performed to confirm the document contains the required `asyncapi` version field.
The loading process is designed with caching as a first-class citizen. Within `packages/asyncapi/src/server/index.tsx`, the `createAsyncAPI` factory function initializes a `schemaMap` (a `Map>`). When `getSchema` is called, the system first checks this cache unless `disableCache` is explicitly enabled. This design prevents redundant network requests or filesystem reads, which is critical in dynamic server environments where documentation might be rendered on every request.
> [!NOTE]
> When using `getSchema` for documents not explicitly listed in the `input` array of `AsyncAPIOptions`, the system issues a console warning and skips caching for the resulting `LoadedDocument` to prevent unintentional memory usage.
Sources: [packages/asyncapi/src/server/index.tsx:75-101](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/server/index.tsx#L75-L101), [packages/asyncapi/src/utils/document/load.ts:7-22](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/utils/document/load.ts#L7-L22)
## Page Building and Data Normalization
The generation flow follows a structured pipeline: `Schema` → `fromSchema` (Builder) → `toStaticData` → `VirtualFile`. The `fromSchema` function in `packages/asyncapi/src/utils/pages/builder.ts` acts as the primary orchestration layer. It iterates over the AsyncAPI object, using a configuration-driven approach to map specification elements to `OutputEntry` structures. These entries define the hierarchical nature of the documentation, distinguishing between "pages" and "groups".
The data flow for generating a page entry is as follows:
1. `fromSchema` initializes the `PagesBuilder` context.
2. The `create()` callback within the builder is invoked for each detected operation or group.
3. `onEntries` in the server index recursively processes these entries.
4. For each individual page, `getPageProps` and `toStaticData` convert the raw schema into properties compatible with the UI layer (TOC, metadata, etc.).
```typescript
// Simplified representation of the entry building process
const list = fromSchema(id, schema.bundled, builderOptions);
function onEntry(entry: PageOutput | OperationOutput) {
const props = getPageProps(entry);
// Pushes to the virtual files collection
files.push({
type: 'page',
path: `${baseDir}/${entry.path}`,
data: { ... } // Compiled props and metadata
});
}
```
Sources: [packages/asyncapi/src/server/index.tsx:110-156](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/server/index.tsx#L110-L156), [packages/asyncapi/src/utils/pages/builder.ts:57-87](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/utils/pages/builder.ts#L57-L87)
## Dereferencing Mechanism
The dereferencing subsystem is an essential step in resolving the complex, interlinked structure of AsyncAPI files. The function `dereferenceBundledDocument` (in `packages/asyncapi/src/utils/document/dereference.ts`) performs a synchronous dereference of the bundled document, creating a normalized schema object that the UI components consume.
Crucially, it tracks original references via a `dereferenceMap`. By providing the `setOriginalRef` callback to `dereferenceSync` from `@fumadocs/api-docs/schema/dereference`, it creates a bidirectional mapping between the flat, dereferenced object and the path where it originally lived. This allows the UI to display the exact original location of any schema node using the `getRawRef` method.
| Component | Responsibility |
| :--- | :--- |
| `dereferenced` | The fully expanded AsyncAPI object tree. |
| `getRawRef` | A function to map an object back to its original JSON Pointer. |
| `bundled` | The raw, unresolved input document. |
Sources: [packages/asyncapi/src/utils/document/dereference.ts:6-34](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/utils/document/dereference.ts#L6-L34)
## Protocol Binding Logic
AsyncAPI defines protocol-specific bindings (e.g., Kafka, MQTT, Solace) via `BindingObject` structures. The subsystem handles these dynamically through a mapping in `packages/asyncapi/src/ui/bindings/protocols/index.ts`. Each protocol implements a `ProtocolBindingDefinition`, which dictates how the specific server, channel, operation, or message binding is rendered.
The `getProtocolBinding` function performs a lookup on the `protocolBindings` object. If an unsupported protocol is encountered, it falls back to `unknownBinding`. This provides a safeguard for the documentation renderer, ensuring that unknown protocols do not crash the UI but rather display a default representation.
```typescript
export function getProtocolBinding(protocol: string): ProtocolBindingDefinition {
const v = protocolBindings[protocol as never];
if (v) return v;
return {
...unknownBinding,
label: protocol,
};
}
```
Sources: [packages/asyncapi/src/ui/bindings/protocols/index.ts:25-55](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/index.ts#L25-L55)
## Static File Generation
Beyond the server-side runtime, the subsystem supports generating documentation at build-time through `generateFiles` in `packages/asyncapi/src/generate-file.ts`. This utility scans the input, converts all pages to text (Markdown/MDX format), and writes them to the specified output directory.
The process supports a watch mode using `chokidar`, which is particularly useful for local documentation development. When files change, the `scan` function recursively traverses the output entries, invoking `toText` for each node to generate its serializable form.
> [!CAUTION]
> The `beforeWrite` lifecycle hook allows for external modification of the `OutputFile[]` array. Since this happens globally before write-to-disk, any destructive changes here will persist in the generated static output.
Sources: [packages/asyncapi/src/generate-file.ts:56-81](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/generate-file.ts#L56-L81)
## Data Models
The following table summarizes key interface structures used across the subsystem to maintain consistency between the raw specification and the rendered documentation.
| Entity | Primary Key | Used By |
| :--- | :--- | :--- |
| `AsyncAPIObject` | `asyncapi` | Parser / Loader |
| `OperationObject` | `action` | UI Layout / Builder |
| `MessageObject` | `payload` | UI Schema Resolver |
| `OutputEntry` | `path` | Page Builder |
| `InternalAsyncAPIMeta` | `action` | Loader Plugin |
Sources: [packages/asyncapi/src/types/asyncapi-3.ts:10-192](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/types/asyncapi-3.ts#L10-L192), [packages/asyncapi/src/server/index.tsx:246-250](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/server/index.tsx#L246-L250)
## Related
- [[API Schema Processing]]
- [[API UI]]
---
## API Schema Processing
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/api-schema-processing
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/api-docs/src/components/schema/client.tsx](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/components/schema/client.tsx)
- [packages/api-docs/src/schema/sample.ts](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/schema/sample.ts)
- [packages/openapi/src/types/openapi.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/types/openapi.ts)
- [packages/openapi/src/ui/operation/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/index.tsx)
- [packages/api-docs/src/components/schema/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/components/schema/index.tsx)
- [packages/api-docs/src/schema/dereference.ts](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/schema/dereference.ts)
- [packages/api-docs/src/schema/to-string.ts](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/schema/to-string.ts)
- [packages/openapi/src/utils/pages/builder.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/pages/builder.ts)
- [packages/openapi/src/utils/pages/preset-auto.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/pages/preset-auto.ts)
- [packages/openapi/src/utils/document/dereference.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/document/dereference.ts)
- [packages/asyncapi/src/utils/schema.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/utils/schema.ts)
- [packages/openapi/src/utils/schema.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/schema.ts)
- [packages/openapi/src/server/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/server/index.tsx)
- [packages/asyncapi/src/utils/document/dereference.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/utils/document/dereference.ts)
- [packages/api-docs/src/schema/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/schema/index.ts)
- [packages/api-docs/src/schema/merge.ts](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/schema/merge.ts)
- [packages/api-docs/src/schema/resolve-ref.ts](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/schema/resolve-ref.ts)
- [packages/api-docs/src/schema/ref.ts](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/schema/ref.ts)
- [packages/asyncapi/src/ui/bindings/shared.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/shared.tsx)
- [packages/openapi/src/requests/media/encode.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/requests/media/encode.ts)
- [packages/openapi/src/utils/get-example-requests.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/get-example-requests.ts)
- [packages/openapi/src/types.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/types.ts)
- [packages/content-collections/src/configuration.ts](https://github.com/blade47/fumadocs/blob/main/packages/content-collections/src/configuration.ts)
- [packages/asyncapi/src/types/asyncapi-3.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/types/asyncapi-3.ts)
API Schema Processing is a robust subsystem responsible for transforming, dereferencing, and visualizing complex API definitions (OpenAPI and AsyncAPI) into user-friendly documentation. At its heart, the system abstracts the hierarchical, reference-heavy nature of API specifications into a flat, navigable structure suitable for web-based UI rendering. It addresses the inherent complexity of resolving `$ref` pointers across massive documents, ensures consistent type representation, and handles the generation of representative example payloads for API requests.
The subsystem serves as the bridge between raw specification data (often deeply nested and non-deterministic) and the documentation components that render interactive UI elements like parameter tables, request body viewers, and nested schema explorations. By decoupling schema processing from the final rendering layer, the system allows for flexible consumption of API data while maintaining high performance through memoized data transformations and static generation utilities.
Interactions with adjacent components are mediated through clear interfaces: document loaders handle raw data retrieval, builders translate API structures into content pages, and UI components consume pre-processed "SchemaUI" data formats. This pipeline ensures that the core documentation engine remains agnostic of specific API versions or protocol-specific implementation details while providing a seamless developer experience for documentation consumers.
## Schema Dereferencing Mechanism
The dereferencing mechanism is a recursive algorithm that traverses JSON Schema or OpenAPI definitions to resolve references (`$ref`) into their actual, defined objects. This process is critical for flattening structures so that the documentation UI can perform lookup operations without needing to perform on-the-fly network or document-wide resolutions.
The primary entry point, `dereferenceSync` in `packages/api-docs/src/schema/dereference.ts`, utilizes `structuredClone` to create a deep copy of the document, ensuring operations are performed on isolated data structures. The algorithm maintains a `visitedNodes` `Set` to prevent infinite loops (a common issue in circular schema definitions). When an object containing `$ref` is encountered, it is passed to `resolveRefSync` which parses the JSON pointer and retrieves the target node from the root document.
> [!IMPORTANT]
> The `dereferenceSync` function employs a `preserveRef` callback option. If this function returns `true` for a given pointer, the algorithm will stop inlining, preserving the original reference pointer in the final object instead of replacing it with the dereferenced content.
Sources: [packages/api-docs/src/schema/dereference.ts:19-65](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/schema/dereference.ts#L19-L65)
## Schema Sample Generation
Generating example request data from a schema is handled by a recursive `traverse` function, inspired by the logic found in `openapi-sampler`. This process is non-trivial because JSON Schema allows for polymorphic constructs (`oneOf`, `anyOf`, `allOf`) and conditional logic (`if/then`) that make payload inference complex.
The sample generator uses a `seenObjectStack` and `refResolving` cache to handle recursive or circular references. When traversing an object, the generator respects flags such as `skipReadOnly` and `skipWriteOnly`, which are crucial for ensuring the generated example matches the actual intended usage (e.g., in a GET request, read-only fields should be included; in a POST request, write-only fields take precedence).
```mermaid
flowchart TD
A["Traverse(schema)"] --> B{Has $ref?}
B -->|Yes| C["ResolveRefSync"]
B -->|No| D{Has Example?}
D -->|Yes| E["Return Example Value"]
D -->|No| F["Type Sampler (Array/Object/String)"]
F --> G["Assemble Payload"]
```
Sources: [packages/api-docs/src/schema/sample.ts:527-663](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/schema/sample.ts#L527-L663)
## Schema Intersection and Merging
When working with `allOf` in JSON Schema, the system must merge properties from multiple schema definitions. The `mergeAllOf` function in `packages/api-docs/src/schema/merge.ts` acts as a recursive processor that performs set intersection on schema constraints.
For instance, when merging two objects, it doesn't just clone the objects; it executes a structural intersection. Key fields like `minimum`, `maximum`, `minLength`, and `maxLength` are computed using `Math.max` or `Math.min` to ensure the final schema maintains the strictest possible constraint.
> [!NOTE]
> The intersection logic for `properties` in `packages/api-docs/src/schema/merge.ts` is additive. If a property exists in both schemas being merged, `intersection` is called recursively on those properties, ensuring deep consistency.
Sources: [packages/api-docs/src/schema/merge.ts:11-27](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/schema/merge.ts#L11-L27)
## Schema-to-String Conversion
The documentation UI frequently needs to display the "Type" of a field (e.g., `array | null`). The `schemaToString` utility handles the conversion of a JSON Schema object into a human-readable type descriptor.
This function uses a bitwise `FormatFlags` enum (`None = 0`, `UseAlias = 1`) to determine whether to use the schema's raw `type` or its `title` alias (often defined by a `$ref` path). This allows the UI to render "Pet" instead of "object" when appropriate.
| Flag | Value | Purpose |
| :--- | :--- | :--- |
| `None` | 0 | Default string conversion (types, unions, intersections). |
| `UseAlias` | 1 | Attempts to resolve a human-readable title or reference name. |
Sources: [packages/api-docs/src/schema/to-string.ts:3-12](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/schema/to-string.ts#L3-L12)
## Page Builder and Preset Auto
The `PagesBuilder` system provides a fluent API for defining how an OpenAPI document is sliced into discrete documentation pages. The `createAutoPreset` function is a high-level utility that abstracts this builder to allow automatic page generation based on configuration (`per: 'operation' | 'tag' | 'file'`).
The `group` method within the builder handles the sorting and hierarchical organization of operation entries. When `groupBy` is set to `tag`, the builder traverses the tags defined in the document, filters operations associated with that tag, and constructs a virtual folder path for each group.
```mermaid
sequenceDiagram
participant B as PagesBuilder
participant P as PresetAuto
participant D as Document
P->>B: extract()
B->>D: iterate paths & methods
D-->>B: return operations & webhooks
P->>P: group(entries, groupBy)
P->>B: create(entry)
```
Sources: [packages/openapi/src/utils/pages/preset-auto.ts:150-260](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/pages/preset-auto.ts#L150-L260)
## Design Trade-offs
| Design Choice | Benefit | Cost |
| :--- | :--- | :--- |
| **Sync Dereferencing** | Simpler recursive logic, avoiding async orchestration in UI components. | Potential blocking during initial document load if the schema is massive. |
| **Deep Object Merging** | Allows precise control over example data inclusion/exclusion logic. | Higher computational overhead compared to shallow structural cloning. |
| **Bitwise Formatting Flags** | Efficient, compact representation of formatting state. | Less readable compared to explicit boolean objects. |
| **Reference Resolution Map** | Enables tracking of the original source of resolved content. | Memory usage increases with document size as references are mapped. |
Sources: [packages/api-docs/src/schema/dereference.ts:19-65](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/schema/dereference.ts#L19-L65), [packages/api-docs/src/schema/to-string.ts:3-6](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/schema/to-string.ts#L3-L6)
## Full Schema Processing Pipeline Example
The following demonstrates how to initialize a document and process it through the builder.
```typescript
import { createOpenAPI } from '@fumadocs/openapi/server';
import { createAutoPreset } from '@fumadocs/openapi/utils/pages/preset-auto';
// 1. Initialize server with an OpenAPI spec
const server = createOpenAPI({
input: ['https://petstore.swagger.io/v2/swagger.json']
});
// 2. Configure automatic page generation
const options = createAutoPreset({
per: 'operation',
groupBy: 'tag'
});
// 3. Generate static pages
const source = await server.staticSource(options);
// Now 'source.files' contains the virtual filesystem paths and page props
```
Sources: [packages/openapi/src/server/index.tsx:111-296](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/server/index.tsx#L111-L296)
## Related
- [[OpenAPI Generation]]
- [[AsyncAPI Generation]]
---
## Code Generation
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/code-generation
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/openapi/src/ui/operation/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/index.tsx)
- [packages/openapi/src/requests/generators/curl.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/requests/generators/curl.ts)
- [packages/openapi/src/ui/operation/usage-tabs.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/usage-tabs.tsx)
- [packages/openapi/src/requests/generators/python.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/requests/generators/python.ts)
- [packages/api-docs/src/schema/sample.ts](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/schema/sample.ts)
- [packages/openapi/src/requests/generators/go.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/requests/generators/go.ts)
- [packages/openapi/src/requests/generators/java.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/requests/generators/java.ts)
- [packages/openapi/src/requests/generators/csharp.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/requests/generators/csharp.ts)
- [packages/openapi/src/requests/media/adapter.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/requests/media/adapter.ts)
- [packages/openapi/src/requests/generators/javascript.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/requests/generators/javascript.ts)
- [packages/openapi/src/utils/pages/preset-auto.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/pages/preset-auto.ts)
- [packages/openapi/src/requests/generators/all.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/requests/generators/all.ts)
- [packages/api-docs/src/codegen.ts](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/codegen.ts)
- [packages/asyncapi/src/ui/operation/message-examples.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/operation/message-examples.tsx)
- [packages/core/src/source/llms.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/llms.ts)
- [packages/openapi/src/utils/get-example-requests.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/get-example-requests.ts)
- [packages/openapi/src/ui/operation/response-tabs.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/response-tabs.tsx)
- [packages/openapi/src/ui/operation/request-tabs.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/request-tabs.tsx)
- [packages/openapi/src/requests/generators/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/requests/generators/index.ts)
- [packages/openapi/src/server/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/server/index.tsx)
- [packages/python/src/convert.ts](https://github.com/blade47/fumadocs/blob/main/packages/python/src/convert.ts)
- [packages/mdx/src/utils/codegen.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/utils/codegen.ts)
- [packages/openapi/src/requests/media/encode.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/requests/media/encode.ts)
- [packages/openapi/src/utils/pages/to-text.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/pages/to-text.ts)
- [packages/openapi/src/utils/pages/to-static-data.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/pages/to-static-data.ts)
- [packages/asyncapi/src/utils/pages/to-text.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/utils/pages/to-text.ts)
- [packages/openapi/src/utils/schema.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/schema.ts)
- [packages/openapi/src/requests/string-utils.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/requests/string-utils.ts)
- [packages/python/fumapy/__init__.py](https://github.com/blade47/fumadocs/blob/main/packages/python/fumapy/__init__.py)
- [packages/openapi/src/scalar/client.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/scalar/client.tsx)
Code Generation in this architecture is a specialized subsystem designed to bridge the gap between static API documentation and actionable, implementation-ready code. Its primary purpose is to transform formal API specifications—specifically OpenAPI—into functional code snippets for various programming languages (e.g., Python, Go, Java, cURL, JavaScript), allowing users to immediately understand how to interact with the documented endpoints.
This subsystem solves the common problem of manual documentation maintenance by dynamically generating code samples from the same source of truth used to render the documentation pages. It acts as an abstraction layer between the API definition and the final UI presentation, enabling extensibility via generator registries while handling the complexities of media type serialization, request parameter encoding, and language-specific syntax formatting.
The architecture centers on a pluggable registry pattern, where `CodeUsageGenerator` implementations are registered and then invoked within the UI layer. This decoupled design allows users to support custom programming languages or override default behavior for specific API endpoints without modifying the core codebase.
## The Generator Registry
The `CodeUsageGeneratorRegistry` is the primary orchestrator for code generation. It maintains a collection of generators keyed by language identifiers, providing a standardized interface for registration, retrieval, and removal.
The mechanism utilizes a factory function `createCodeUsageGeneratorRegistry` that encapsulates the map-based storage. A critical aspect of this registry is that it supports inheritance, allowing new registries to be initialized with existing generator sets, which facilitates modular configurations.
```typescript
// Initializing a registry with inherited generators
const registry = createCodeUsageGeneratorRegistry(ctx.codeUsages);
// Adding an inline generator for a custom language
registry.addInline(gen);
```
Sources: [packages/api-docs/src/codegen.ts:37-72](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/codegen.ts#L37-L72), [packages/openapi/src/ui/operation/usage-tabs.tsx:74-90](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/usage-tabs.tsx#L74-L90)
## Request Data Flow
The flow of code generation starts when an operation is rendered in the UI. The process involves sampling raw request data, encoding it via media type adapters, and finally passing the resulting structured request object to a specific code generator.
```mermaid
flowchart TD
A["Operation UI"] --> B["getExampleRequests"]
B --> C["encodeRequestData"]
C --> D["Media Adapters (JSON, XML, etc.)"]
D --> E["UsageTabs"]
E --> F["Selected Code Generator"]
F --> G["Formatted Code Output"]
```
Sources: [packages/openapi/src/ui/operation/index.tsx:77-80](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/index.tsx#L77-L80), [packages/openapi/src/utils/get-example-requests.ts:23-76](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/get-example-requests.ts#L23-L76)
## Media Type Adapters
Media adapters handle the transformation of request bodies into language-specific formats and actual byte arrays for transport. Each adapter implements two primary methods: `encode` (for runtime usage, like a playground) and `generateExample` (for static code generation).
> [!NOTE]
> The `generateExample` function receives a `MediaContext` object. This context allows the generator to "inject" language-specific requirements—such as imports—back into the main generator's scope (e.g., `addImport` in Go/Java).
Sources: [packages/openapi/src/requests/media/adapter.ts:40-58](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/requests/media/adapter.ts#L40-L58), [packages/openapi/src/requests/media/adapter.ts:180-182](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/requests/media/adapter.ts#L180-L182)
## Request Parameter Serialization
Parameters (path, query, header, cookie) must be serialized according to their schema and "serialization style". The `encodeRequestData` function performs this logic:
1. It iterates over defined parameter types.
2. It attempts to find a specific media encoder (if defined via content).
3. If no media encoder exists, it defaults to standard serializers (`serializePathParameter`, `serializeQueryParameter`, etc.).
The `serializeSimple` function acts as the foundational mechanism for basic key-value pairs, while specialized functions handle exploded styles (where array elements or object properties are expanded).
Sources: [packages/openapi/src/requests/media/encode.ts:17-70](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/requests/media/encode.ts#L17-L70), [packages/openapi/src/requests/media/encode.ts:83-95](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/requests/media/encode.ts#L83-L95)
## Generator Implementation Strategies
Generators utilize shared string utilities to maintain consistency. The `string-utils.ts` file provides escaping helpers like `doubleQuote`, `singleQuote`, and `tripleDoubleQuote` (for Python multiline strings), which are essential for security and syntax correctness when building code strings.
| Language | Primary Serialization Mechanism |
| :--- | :--- |
| **cURL** | `-F` (multipart) or `-d` (data) flags |
| **Python** | `requests.request` with dictionary objects |
| **Go** | `http.NewRequest` with `strings.Reader` or `bytes.Buffer` |
| **Java** | `java.net.http.HttpRequest` with `BodyPublishers` |
| **C#** | `HttpClient` using `MultipartFormDataContent` or `StringContent` |
Sources: [packages/openapi/src/requests/string-utils.ts:45-73](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/requests/string-utils.ts#L45-L73), [packages/openapi/src/requests/generators/python.ts:8-58](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/requests/generators/python.ts#L8-L58)
## Lifecycle of a Code Snippet
The actual render of a code block in the UI involves a listener pattern to ensure real-time updates when an example request changes (e.g., if a user selects a different example from a dropdown).
1. `UsageTab` uses `useOperationContext` to `addListener`.
2. The `useEffect` hook subscribes to state updates.
3. Upon triggering `setExample` or `setExampleData`, the listener updates the internal `data` state.
4. `useMemo` triggers a re-generation of the code snippet via the registered `codegen.generate` function.
```mermaid
sequenceDiagram
participant UI as Operation UI
participant Ctx as OperationContext
participant Tab as UsageTab
participant Gen as Generator
UI->>Ctx: setExample(newId)
Ctx-->>Tab: callback(encodedData)
Tab->>Tab: setData(encodedData)
Tab->>Gen: generate(data, {mediaAdapters})
Gen-->>Tab: string (formatted code)
```
Sources: [packages/openapi/src/ui/operation/usage-tabs.tsx:137-185](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/usage-tabs.tsx#L137-L185), [packages/openapi/src/ui/operation/context.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/context.tsx)
## Related
- [[OpenAPI Generation]]
---
## API UI
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/api-ui
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/openapi/src/ui/operation/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/index.tsx)
- [packages/asyncapi/src/ui/operation/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/operation/index.tsx)
- [packages/openapi/src/ui/operation/usage-tabs.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/usage-tabs.tsx)
- [packages/asyncapi/src/ui/contexts/api.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/contexts/api.tsx)
- [packages/api-docs/src/components/schema/client.tsx](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/components/schema/client.tsx)
- [packages/asyncapi/src/ui/components/server-select.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/components/server-select.tsx)
- [packages/openapi/src/ui/contexts/api.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/contexts/api.tsx)
- [packages/openapi/src/ui/operation/response-tabs.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/response-tabs.tsx)
- [packages/asyncapi/src/ui/operation/message-examples.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/operation/message-examples.tsx)
- [packages/asyncapi/src/ui/bindings/shared.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/shared.tsx)
- [packages/asyncapi/src/ui/api-page.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/api-page.tsx)
- [packages/openapi/src/ui/operation/request-tabs.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/request-tabs.tsx)
- [packages/asyncapi/src/ui/bindings/protocols/solace.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/solace.tsx)
- [packages/asyncapi/src/ui/bindings/protocols/amqp.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/amqp.tsx)
- [packages/asyncapi/src/ui/bindings/protocols/http.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/http.tsx)
- [packages/asyncapi/src/ui/bindings/accordion-bindings.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/accordion-bindings.tsx)
- [packages/openapi/src/ui/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/index.tsx)
- [packages/openapi/src/ui/base.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/base.tsx)
- [packages/asyncapi/src/ui/bindings/protocols/mqtt.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/mqtt.tsx)
- [packages/asyncapi/src/ui/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/index.tsx)
- [packages/asyncapi/src/ui/bindings/protocols/pulsar.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/pulsar.tsx)
- [packages/asyncapi/src/ui/bindings/protocols/ibmmq.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/ibmmq.tsx)
- [packages/asyncapi/src/ui/bindings/protocols/anypointmq.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/anypointmq.tsx)
- [packages/asyncapi/src/ui/bindings/protocols/jms.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/jms.tsx)
- [packages/asyncapi/src/ui/bindings/protocols/kafka.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/kafka.tsx)
- [packages/asyncapi/src/ui/bindings/protocols/sqs.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/sqs.tsx)
- [packages/asyncapi/src/ui/bindings/protocols/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/index.ts)
- [packages/asyncapi/src/ui/bindings/protocols/sns.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/sns.tsx)
- [packages/asyncapi/src/ui/bindings/protocols/nats.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/nats.tsx)
- [packages/asyncapi/src/ui/bindings/protocols/unknown.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/unknown.tsx)
API UI serves as the rendering layer for API documentation, transforming machine-readable specifications (OpenAPI and AsyncAPI) into interactive, human-readable documentation. It acts as a bridge between the core document structure and the presentation layer, enabling features like request playground testing, code snippet generation, and complex protocol binding visualization.
By decoupling document parsing from UI rendering, the API UI system enables a pluggable architecture. Developers can provide custom renderers for different components of the API lifecycle—such as operations, response tabs, or playground UI—allowing for deep customization within a standard documentation framework.
The system relies heavily on context providers for state management, particularly for global state like server selection and render-time configurations (e.g., media type adapters, shiki highlighters). This ensures consistent behavior across different documentation components while maintaining the performance benefits of a client-side reactive rendering pipeline.
## Operation Rendering Mechanism
The `Operation` component (found in both OpenAPI and AsyncAPI) is the central entry point for rendering API documentation. In the OpenAPI implementation, it handles the lifecycle of an individual operation by aggregating sections such as Request Body, Parameters, Responses, and Callbacks.
The control flow follows a structured layout approach:
1. It initializes the `RenderContext` to access configuration and schema data.
2. If `showTitle` is true, it renders the heading, applying an `id` for auto-anchoring (based on summary, operationId, or path).
3. It conditionally branches based on the operation type (`operation` vs `webhook`), and renders subsections sequentially, injecting the results into a `renderOperationLayout` slot system if provided.
Sources: [packages/openapi/src/ui/operation/index.tsx:42-408](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/index.tsx#L42-L408)
```mermaid
flowchart TD
A["Operation"] --> B{"Type?"}
B -->|operation| C["Render header, playground, auth, params, body, responses"]
B -->|webhook| D["Render layout"]
C --> E["Inject into renderOperationLayout slots"]
E --> F["Wrap in ServerProvider (if servers exist)"]
```
Sources: [packages/openapi/src/ui/operation/index.tsx:318-408](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/index.tsx#L318-L408)
## Server State Management
The `ServerProvider` manages the lifecycle of server configurations, which are critical for playground interactions and example generation. It utilizes `localStorage` to persist user-selected server configurations (selected ID and variables), ensuring that user preferences remain consistent across page reloads.
- `getDefaultValues`: Extracts initial variable values from the server definition.
- `setServerVariables`: Merges new variable values into the existing state and triggers a `localStorage` update.
- `setServer`: Switches the active server and recalculates variables based on the new definition.
Sources: [packages/openapi/src/ui/contexts/api.tsx:47-123](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/contexts/api.tsx#L47-L123), [packages/asyncapi/src/ui/contexts/api.tsx:47-124](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/contexts/api.tsx#L47-L124)
> [!NOTE]
> When multiple OpenAPI/AsyncAPI instances exist on the same host, use `storageKeyPrefix` to prevent state conflicts between `localStorage` keys.
Sources: [packages/openapi/src/ui/index.tsx:212-218](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/index.tsx#L212-L218)
## Protocol Binding Visualization
The AsyncAPI UI subsystem supports complex protocol-specific bindings through a modular registry pattern. Each binding type (e.g., Kafka, AMQP, MQTT) defines its own UI components for rendering server, channel, operation, or message configurations.
The `AccordionBindings` component dynamically resolves the appropriate renderer by looking up the binding protocol in the `protocolBindings` registry.
```typescript
// The lookup pattern in accordion-bindings.tsx
const definition = getProtocolBinding(entry.protocol);
// definition.Server, definition.Channel, etc., provide UI for specific fields.
```
Sources: [packages/asyncapi/src/ui/bindings/accordion-bindings.tsx:11-47](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/accordion-bindings.tsx#L11-L47)
### Available Protocol Bindings
| Protocol | Label | Key Source |
| :--- | :--- | :--- |
| Kafka | Kafka | [kafka.tsx:189-199](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/kafka.tsx#L189-L199) |
| AMQP | AMQP | [amqp.tsx:215-222](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/amqp.tsx#L215-L222) |
| MQTT | MQTT | [mqtt.tsx:209-217](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/mqtt.tsx#L209-L217) |
| Solace | Solace | [solace.tsx:140-146](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/solace.tsx#L140-L146) |
| Pulsar | Apache Pulsar | [pulsar.tsx:109-116](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/pulsar.tsx#L109-L116) |
Sources: [packages/asyncapi/src/ui/bindings/protocols/index.ts:25-45](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/bindings/protocols/index.ts#L25-L45)
## Schema UI and Interaction
The `SchemaUI` component provides a recursive interface for browsing complex JSON schema types. It maintains its state through a `Context` that tracks the current path into the schema object.
- `decodePath`: Parses a URL-encoded string to determine the current browsing depth.
- `ObjectProperty`: Renders an individual schema property and provides a "copy link" functionality that encodes the current path into the clipboard URL.
> [!CAUTION]
> The `decodePath` mechanism expects path segments separated by `|` and `\0`. Tampering with these query parameters in the URL may cause rendering failures in the `SchemaUI`.
Sources: [packages/api-docs/src/components/schema/client.tsx:603-616](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/components/schema/client.tsx#L603-L616)
## Example Generation Workflow
The API UI generates code usage examples using a registry pattern. When a user changes the selected example (e.g., from a dropdown), the `UsageTabs` component broadcasts this event through a listener pattern.
### Call Chain: Changing Example
1. `UsageTabsSelector` triggers `setKey` from `useOperationContext`.
2. `useOperationContext` propagates the new state to all registered listeners.
3. `UsageTab` (the active tab) receives the update via `addListener`.
4. `codegen.generate` is invoked with the new data to refresh the displayed code block.
Sources: [packages/openapi/src/ui/operation/usage-tabs.tsx:102-180](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/usage-tabs.tsx#L102-L180)
## Design Trade-offs
| Design Choice | Benefit | Cost |
| :--- | :--- | :--- |
| Slot-based layout rendering | High flexibility for custom documentation designs | Higher implementation complexity for default renderers |
| JSON Schema dereferencing | Simplifies rendering logic (no recursive refs) | Increased memory usage for large bundled specifications |
| Context-based state | Simplifies cross-component data access | Forces components to be nested within specific providers |
Sources: [packages/openapi/src/ui/base.tsx:97-139](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/base.tsx#L97-L139), [packages/openapi/src/ui/operation/index.tsx:318-389](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/index.tsx#L318-L389)
## Related
- [[OpenAPI Generation]]
- [[AsyncAPI Generation]]
---
## Layout System
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/layout-system
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/radix-ui/src/layouts/notebook/slots/sidebar.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/layouts/notebook/slots/sidebar.tsx)
- [packages/base-ui/src/layouts/notebook/slots/sidebar.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/notebook/slots/sidebar.tsx)
- [packages/base-ui/src/components/sidebar/base.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/sidebar/base.tsx)
- [packages/base-ui/src/layouts/flux/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/flux/index.tsx)
- [packages/radix-ui/src/layouts/flux/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/layouts/flux/index.tsx)
- [packages/base-ui/src/layouts/notebook/client.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/notebook/client.tsx)
- [packages/radix-ui/src/layouts/notebook/client.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/layouts/notebook/client.tsx)
- [packages/base-ui/src/layouts/shared/client.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/shared/client.tsx)
- [packages/radix-ui/src/layouts/shared/client.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/layouts/shared/client.tsx)
- [packages/radix-ui/src/legacy/layout.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/legacy/layout.tsx)
- [packages/base-ui/src/layouts/notebook/slots/container.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/notebook/slots/container.tsx)
- [packages/radix-ui/src/layouts/notebook/slots/container.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/layouts/notebook/slots/container.tsx)
- [packages/core/src/framework/waku.tsx](https://github.com/blade47/fumadocs/blob/main/packages/core/src/framework/waku.tsx)
- [packages/base-ui/src/layouts/notebook/slots/header.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/notebook/slots/header.tsx)
- [packages/radix-ui/src/layouts/notebook/slots/header.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/layouts/notebook/slots/header.tsx)
- [packages/base-ui/src/layouts/home/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/home/index.tsx)
- [packages/radix-ui/src/layouts/home/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/layouts/home/index.tsx)
- [packages/base-ui/src/layouts/notebook/page/slots/container.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/notebook/page/slots/container.tsx)
- [packages/base-ui/src/layouts/notebook/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/notebook/index.tsx)
- [packages/radix-ui/src/layouts/notebook/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/layouts/notebook/index.tsx)
- [packages/radix-ui/src/layouts/shared/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/layouts/shared/index.tsx)
- [packages/core/src/framework/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/core/src/framework/index.tsx)
- [packages/base-ui/src/layouts/flux/page/slots/container.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/flux/page/slots/container.tsx)
- [packages/base-ui/src/layouts/flux/slots/sidebar.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/flux/slots/sidebar.tsx)
- [packages/core/src/framework/tanstack.tsx](https://github.com/blade47/fumadocs/blob/main/packages/core/src/framework/tanstack.tsx)
- [packages/base-ui/src/layouts/shared/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/shared/index.tsx)
- [packages/base-ui/src/provider/waku.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/provider/waku.tsx)
- [packages/radix-ui/src/provider/waku.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/provider/waku.tsx)
- [packages/base-ui/src/provider/tanstack.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/provider/tanstack.tsx)
- [packages/core/src/framework/next.tsx](https://github.com/blade47/fumadocs/blob/main/packages/core/src/framework/next.tsx)
The Layout System provides a standardized, compositional framework for structuring documentation sites. It addresses the common challenge of fragmented UI components by offering unified primitives for sidebars, headers, and content areas, ensuring consistent navigation and layout behavior across different documentation styles (Notebook and Flux).
At its core, the system utilizes a hierarchical layout strategy driven by shared context providers. These providers encapsulate cross-cutting concerns like navigation states, theme switching, and language selection, allowing individual layout components to remain decoupled from the specific configuration of the host application.
By separating the "slots" (extensible areas of the UI) from the underlying implementation logic, the system allows for modular customization. Whether using the sophisticated "Notebook" style—focused on hierarchical content trees—or the more modern, interactive "Flux" style, developers interact with a predictable interface to inject custom branding, toolsets, and page content.
## Sidebar Architecture and State Management
The Sidebar subsystem acts as the primary navigation engine. It is built upon a `SidebarProvider` that manages open/collapsed states and responsive transitions (drawer vs. full mode). The sidebar logic is defined in `packages/base-ui/src/components/sidebar/base.tsx`, which serves as the foundation for both Notebook and Radix-UI layout variants.
The system uses a `FolderContext` to track hierarchy depth and collapsible states recursively. This ensures that tree-based navigation correctly handles indentation and trigger behavior.
```mermaid
flowchart TD
A[SidebarProvider] --> B[SidebarContext]
B --> C[SidebarContent]
C --> D[SidebarViewport]
D --> E[PageTreeRenderer]
E --> F[SidebarFolder]
F --> G[FolderContext]
G --> H[SidebarItem]
```
Sources: [packages/base-ui/src/components/sidebar/base.tsx:75-112](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/sidebar/base.tsx#L75-L112), [packages/base-ui/src/components/sidebar/base.tsx:298-305](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/sidebar/base.tsx#L298-L305)
> [!TIP]
> The sidebar uses a `timerRef` in `SidebarContent` to handle hover states on collapsed sidebars. If the mouse leaves the viewport, it triggers a 500ms delay before closing, improving usability on desktop.
Sources: [packages/base-ui/src/components/sidebar/base.tsx:146-179](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/sidebar/base.tsx#L146-L179)
## The Notebook Layout Mechanism
The Notebook layout implementation defines a rigid grid structure to manage the documentation lifecycle. It relies on `LayoutBody` (in `client.tsx`) to initialize the `LayoutContext`, which bundles navigational items, header/sidebar slots, and scroll-transparency logic.
The grid layout is computed dynamically in the `Container` component:
1. Calculates `pageCol` based on layout width and sidebar/TOC column widths.
2. Sets grid areas: `sidebar`, `header`, `main`, and `toc`.
3. Injects CSS variables (`--fd-sidebar-col`, etc.) that control the layout responsiveness.
```mermaid
sequenceDiagram
participant User
participant LayoutBody
participant LayoutContext
participant Container
User->>LayoutBody: Initialize DocsLayout
LayoutBody->>LayoutContext: Provide (slots, navItems, props)
LayoutBody->>Container: Render container
Container->>Container: Calculate grid-template-columns
Container-->>User: Render document body
```
Sources: [packages/base-ui/src/layouts/notebook/client.tsx:67-124](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/notebook/client.tsx#L67-L124), [packages/base-ui/src/layouts/notebook/slots/container.tsx:24-54](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/notebook/slots/container.tsx#L24-L54)
## Link Item Resolution
Link items are the building blocks of navigation. They are resolved via `resolveLinkItems` in `packages/base-ui/src/layouts/shared/index.tsx`. The system supports multiple types, filtering them by their `on` property ('menu', 'nav', 'all') to ensure they appear only where intended.
| Item Type | Key Properties | Purpose |
| :--- | :--- | :--- |
| `MainItemType` | `url`, `text`, `description` | Primary navigation links. |
| `IconItemType` | `url`, `icon`, `label` | Social/external links. |
| `ButtonItemType` | `url`, `icon`, `text` | Action-oriented navigation. |
| `MenuItemType` | `items`, `text` | Nested dropdown menus. |
| `CustomItemType` | `children` | Allows arbitrary UI injection. |
Sources: [packages/base-ui/src/layouts/shared/index.tsx:256-277](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/shared/index.tsx#L256-L277)
## Navigation Transparency and Scroll
The Notebook layout includes a transparency feature for navigation, enabled by the `transparentMode` option ('top', 'always', or 'none'). When 'top' is selected, it uses the `useIsScrollTop` hook to toggle the transparency state as the user scrolls.
> [!NOTE]
> `isNavTransparent` is passed via `LayoutContext` to the `Header` component, which applies a `data-transparent` attribute to its container. This allows CSS to reactively change background opacity based on scroll state.
Sources: [packages/base-ui/src/layouts/notebook/client.tsx:82-83](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/notebook/client.tsx#L82-L83), [packages/base-ui/src/layouts/notebook/slots/header.tsx:38-41](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/notebook/slots/header.tsx#L38-L41)
## Flux Layout Strategy
The Flux layout differs from the Notebook layout by prioritizing a mobile-first, floating navigation panel. It uses a custom `NavigationPanel` component that persists at the bottom of the screen on mobile or becomes a fixed floating element on larger screens via CSS grid or flexbox manipulation.
The Flux layout context provider also exposes `slots` for search triggers and layout tabs, but centers its interaction pattern around the `NavigationPanel` component, which manages search modal states and navigation toolsets.
Sources: [packages/base-ui/src/layouts/flux/index.tsx:62-76](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/flux/index.tsx#L62-L76)
## Framework Integration
The system supports multiple frameworks (Next.js, Waku, Tanstack) via the `FrameworkProvider` pattern. This decouples layout logic from the framework-specific router (e.g., `usePathname`, `push`).
| Design Choice | Benefit | Cost |
| :--- | :--- | :--- |
| FrameworkProvider | Agnostic router and link components. | Adds indirection to core UI components. |
| Slot Pattern | Allows extreme extensibility in headers/sidebars. | Requires consistent API surface across slots. |
| Grid-based Layout | Precise control over complex responsive areas. | Requires careful management of CSS grid template strings. |
Sources: [packages/core/src/framework/index.tsx:53-73](https://github.com/blade47/fumadocs/blob/main/packages/core/src/framework/index.tsx#L53-L73)
### Full Example: Configuring a Custom Header Slot
To customize the header behavior in a Notebook layout, one can inject a custom component using the `slots` prop provided by the `DocsLayout`.
```typescript
import { DocsLayout } from 'fumadocs-ui/layouts/notebook';
// Custom header implementation
const CustomHeader = () => (
);
export default function RootLayout({ children }) {
return (
{children}
);
}
```
Sources: [packages/base-ui/src/layouts/notebook/index.tsx:11-21](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/notebook/index.tsx#L11-L21)
## Related
- [[Sidebar Navigation]]
- [[Table of Contents]]
---
## Sidebar Navigation
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/sidebar-navigation
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/radix-ui/src/layouts/notebook/slots/sidebar.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/layouts/notebook/slots/sidebar.tsx)
- [packages/base-ui/src/layouts/notebook/slots/sidebar.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/notebook/slots/sidebar.tsx)
- [packages/base-ui/src/layouts/home/slots/header.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/home/slots/header.tsx)
- [packages/base-ui/src/components/sidebar/base.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/sidebar/base.tsx)
- [packages/core/src/source/page-tree/builder.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/page-tree/builder.ts)
- [packages/radix-ui/src/components/sidebar/base.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/sidebar/base.tsx)
- [packages/radix-ui/src/components/sidebar/page-tree.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/sidebar/page-tree.tsx)
- [packages/radix-ui/src/components/sidebar/tabs/dropdown.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/sidebar/tabs/dropdown.tsx)
- [packages/radix-ui/src/layouts/flux/slots/sidebar.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/layouts/flux/slots/sidebar.tsx)
- [packages/base-ui/src/layouts/flux/slots/sidebar.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/flux/slots/sidebar.tsx)
- [packages/radix-ui/src/components/sidebar/link-item.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/sidebar/link-item.tsx)
- [packages/radix-ui/src/utils/use-footer-items.ts](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/utils/use-footer-items.ts)
- [packages/base-ui/src/components/sidebar/page-tree.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/sidebar/page-tree.tsx)
- [packages/base-ui/src/layouts/notebook/slots/header.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/notebook/slots/header.tsx)
- [packages/base-ui/src/components/sidebar/tabs/dropdown.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/sidebar/tabs/dropdown.tsx)
- [packages/base-ui/src/layouts/flux/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/flux/index.tsx)
- [packages/radix-ui/src/layouts/flux/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/layouts/flux/index.tsx)
- [packages/base-ui/src/layouts/notebook/client.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/notebook/client.tsx)
- [packages/radix-ui/src/layouts/home/slots/header.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/layouts/home/header.tsx)
- [packages/radix-ui/src/layouts/notebook/client.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/layouts/notebook/client.tsx)
- [packages/core/src/source/llms.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/llms.ts)
- [packages/base-ui/src/components/sidebar/tabs/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/sidebar/tabs/index.tsx)
- [packages/radix-ui/src/layouts/shared/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/layouts/shared/index.tsx)
- [packages/base-ui/src/components/sidebar/link-item.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/sidebar/link-item.tsx)
- [packages/radix-ui/src/components/sidebar/tabs/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/sidebar/tabs/index.tsx)
- [packages/radix-ui/src/layouts/notebook/slots/header.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/layouts/notebook/slots/header.tsx)
- [packages/base-ui/src/layouts/shared/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/shared/index.tsx)
Sidebar navigation is a foundational subsystem that provides structural document hierarchy visualization and site navigation. It serves as the primary way for users to traverse complex documentation content, bridging the gap between raw data structures (like page trees) and a functional UI. By decoupling data rendering (the tree) from structural UI concerns (collapsible folders, drawers, scroll tracking), the system enables developers to inject layout-specific components while maintaining consistent navigation logic.
The design embodies a clear separation between the logic of "being a sidebar"—handling provider state, media queries, and keyboard navigation—and the specific layout representation, such as the `notebook` layout, which supports desktop collapsing and mobile drawers. This hierarchy ensures that logic like `useFolderDepth` or `useSidebar` remains stable across different UI variations while allowing specific slots to render custom elements like tabs, footers, or banners.
Interaction with adjacent components is primarily driven by context providers (`SidebarProvider`) that manage shared state such as open/collapsed status and navigation hooks. The system also tightly integrates with document loading infrastructure, ensuring that the navigation automatically reflects the site's directory structure via the page-tree loader.
## The Sidebar Architecture
The sidebar subsystem is built as a set of decoupled functional components centered around `SidebarProvider`. The provider acts as the state arbiter for the entire navigation tree, managing whether the sidebar is in `drawer` or `full` mode based on media queries, and holding a ref to `closeOnRedirect` to prevent premature closure during site navigation.
```mermaid
flowchart TD
A["SidebarProvider"] --> B["SidebarContent"]
A --> C["SidebarDrawerContent"]
B --> D["SidebarViewport"]
D --> E["SidebarPageTree"]
E --> F["SidebarFolder / SidebarItem"]
```
Sources: [packages/base-ui/src/components/sidebar/base.tsx:75-112](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/sidebar/base.tsx#L75-L112)
## Sidebar State and Context
The subsystem uses React Context to share state across deeply nested nodes (like folder triggers or individual links). The state is split into `SidebarContext` and `FolderContext`.
* **`SidebarContext`**: Manages the global sidebar lifecycle (drawer vs. full mode, global collapse status).
* **`FolderContext`**: Tracks hierarchical concerns for collapsible items, including depth and trigger status.
> [!NOTE]
> The `useSidebar` hook provides `closeOnRedirect`, a `RefObject` allowing components to inhibit the standard sidebar closure behavior during a page transition. Setting `closeOnRedirect.current = false` inside an click handler effectively disables the automatic cleanup logic defined in the `SidebarProvider`.
Sources: [packages/base-ui/src/components/sidebar/base.tsx:32-73](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/sidebar/base.tsx#L32-L73)
## Page Tree Rendering Pipeline
The sidebar displays content by rendering a page tree, which is generated via `createPageTreeRenderer`. This function creates a bridge between the raw data object and the UI components provided to it.
1. **Registration**: A map of components (`SidebarFolder`, `SidebarItem`, etc.) is passed to `createPageTreeRenderer`.
2. **Dispatch**: The renderer traverses the tree recursively; for each node type (`folder`, `page`, `separator`), it fetches the corresponding UI component from the context.
3. **Recursion**: Folders trigger a nested `renderList` call to process their children, maintaining a running depth tally via `useFolderDepth`.
> [!TIP]
> The system allows overriding the UI for specific tree node types by passing `components` (e.g., `Folder`, `Item`, `Separator`) directly into the sidebar props, allowing for rich customization while reusing the built-in tree traversal logic.
Sources: [packages/radix-ui/src/components/sidebar/page-tree.tsx:31-98](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/sidebar/page-tree.tsx#L31-L98)
## Desktop vs. Drawer Mode Selection
The component determines its display strategy using `useMediaQuery`. This logic is encapsulated within `SidebarProvider`, ensuring that the rest of the application remains agnostic to the current view mode (drawer on mobile, fixed/full-width on desktop).
| Mode | Trigger | Behavior |
| :--- | :--- | :--- |
| `drawer` | `SidebarTrigger` | Sidebar becomes an overlayed side-drawer with an active `RemoveScroll` blocker. |
| `full` | `SidebarCollapseTrigger` | Sidebar is either fixed or hidden/minimized on the side of the main container. |
Sources: [packages/base-ui/src/components/sidebar/base.tsx:84-84](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/sidebar/base.tsx#L84-L84)
## Scroll Management and Auto-Navigation
The `useAutoScroll` hook is vital for navigation UX: when a sidebar item becomes `active`, it ensures that the item is scrolled into view within the `SidebarViewport`.
```typescript
export function useAutoScroll(active: boolean, ref: RefObject) {
const { mode } = useSidebar();
useEffect(() => {
if (active && ref.current) {
scrollIntoView(ref.current, {
boundary: document.getElementById(mode === 'drawer' ? 'nd-sidebar-mobile' : 'nd-sidebar'),
scrollMode: 'if-needed',
});
}
}, [active, mode, ref]);
}
```
This ensures that regardless of whether the user is in a drawer or full-width view, the navigation tree automatically highlights and centers the user's current location.
Sources: [packages/base-ui/src/components/sidebar/base.tsx:407-418](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/sidebar/base.tsx#L407-L418)
## Design Trade-offs
| Choice | Benefit | Cost |
| :--- | :--- | :--- |
| Context-based UI | High flexibility; easy to override components. | Adds overhead and potential for "Missing Context" errors. |
| Hook-based State | Simplifies inter-node communication. | Harder to debug state flows without specialized React DevTools. |
| Media Query Resolution | Responsive behavior out of the box. | Requires a layout wrapper to function; breaks if missing provider. |
Sources: [packages/base-ui/src/components/sidebar/base.tsx:116-120](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/sidebar/base.tsx#L116-L120)
## Related
- [[Layout System]]
- [[Page Trees]]
---
## Table of Contents
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/table-of-contents
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/core/src/toc.tsx](https://github.com/blade47/fumadocs/blob/main/packages/core/src/toc.tsx)
- [packages/base-ui/src/components/toc/default.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/toc/default.tsx)
- [packages/radix-ui/src/components/toc/default.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/toc/default.tsx)
- [packages/base-ui/src/components/toc/clerk.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/toc/clerk.tsx)
- [packages/radix-ui/src/components/toc/clerk.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/toc/clerk.tsx)
- [packages/base-ui/src/layouts/notebook/page/slots/toc.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/notebook/page/slots/toc.tsx)
- [packages/radix-ui/src/layouts/notebook/page/slots/toc.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/layouts/notebook/page/slots/toc.tsx)
- [packages/base-ui/src/layouts/home/slots/header.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/home/slots/header.tsx)
- [packages/core/src/mdx-plugins/remark-structure.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-structure.ts)
- [packages/preview/src/pages/[...slugs].tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/pages/%5B...slugs%5D.tsx)
- [packages/core/src/mdx-plugins/rehype-toc.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/rehype-toc.ts)
- [packages/radix-ui/src/utils/use-footer-items.ts](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/utils/use-footer-items.ts)
- [packages/base-ui/src/components/toc/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/toc/index.tsx)
- [packages/openapi/src/ui/components/heading.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/components/heading.tsx)
- [packages/radix-ui/src/components/toc/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/toc/index.tsx)
- [packages/asyncapi/src/ui/components/heading.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/components/heading.tsx)
- [packages/api-docs/src/components/select-tab.tsx](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/components/select-tab.tsx)
- [packages/basehub/src/toc.tsx](https://github.com/blade47/fumadocs/blob/main/packages/basehub/src/toc.tsx)
- [packages/base-ui/src/layouts/flux/page/slots/toc.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/flux/page/slots/toc.tsx)
- [packages/radix-ui/src/layouts/flux/page/slots/toc.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/layouts/flux/page/slots/toc.tsx)
- [packages/core/src/source/llms.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/llms.ts)
- [packages/core/src/mdx-plugins/remark-heading.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-heading.ts)
- [packages/radix-ui/src/layouts/home/slots/header.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/layouts/home/slots/header.tsx)
- [packages/base-ui/src/components/heading.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/heading.tsx)
- [packages/openapi/src/utils/pages/to-static-data.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/pages/to-static-data.ts)
- [packages/radix-ui/src/components/heading.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/heading.tsx)
- [packages/sanity/src/client.ts](https://github.com/blade47/fumadocs/blob/main/packages/sanity/src/client.ts)
- [packages/base-ui/src/components/inline-toc.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/inline-toc.tsx)
- [packages/radix-ui/src/components/inline-toc.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/inline-toc.tsx)
- [packages/base-ui/src/layouts/notebook/slots/header.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/notebook/slots/header.tsx)
The Table of Contents (TOC) subsystem in Fumadocs is a highly reactive, scroll-aware navigation component designed to synchronize with the page's DOM state. It bridges the gap between static content generation (MDX parsing) and dynamic UI feedback, allowing users to track their current position in long-form documentation seamlessly.
The subsystem operates through a multi-layered architecture: a core observation layer that detects intersection changes, a transformation layer that translates these observations into visual state, and a UI layer that maps headings to interactive navigation items. By utilizing `IntersectionObserver` coupled with a custom notification system, the TOC ensures high-performance tracking without manual scroll listeners.
This system effectively solves the "floating navigation" problem in complex technical documentation, where users need context-aware markers that stay updated as they navigate. Its modularity permits various visual implementations (like the "clerk" style vs. the "default" style) while relying on a unified set of reactive primitives for the underlying logic.
## Observation Logic and Reactive State
The core of the TOC logic resides in the `Observer` class located in `packages/core/src/toc.tsx`. This class manages the state of all tracked items and orchestrates the `IntersectionObserver` instances. When `setItems` is called, it reconciles the provided items list with the DOM, clearing existing observers and initializing new ones on the corresponding heading elements.
The `callback` method is the heart of the synchronization loop. It processes incoming intersection entries, maintaining an `active` state for each heading. The system handles scenarios where no headings are intersecting by defaulting to the nearest heading in the viewport, which prevents the TOC from showing an empty state during rapid scrolling.
```mermaid
flowchart TD
A[IntersectionObserver Callback] --> B{Entries Exist?}
B -- Yes --> C[Update items
active state]
C --> D{Has Active Item?}
D -- No --> E[Calculate
closest heading
as fallback]
E --> F[Update
State]
D -- Yes --> F
```
Sources: [packages/core/src/toc.tsx:282-331](https://github.com/blade47/fumadocs/blob/main/packages/core/src/toc.tsx#L282-L331)
> [!NOTE]
> The `Observer` class uses a `Map`-like approach internally to maintain a `Set` of `ChangeListener` listeners. Whenever the internal item list changes via `update`, all registered listeners are notified, enabling UI components to re-render reactively.
## The UI Transformation Pipeline
While the `core` package handles state, UI packages (like `base-ui` and `radix-ui`) transform this state into visual representation using SVGs and CSS variables. The `TOCItems` component calculates the layout of the TOC path, creating a dynamic visual "track" that follows the current scroll position.
The calculation logic in `default.tsx` maps the nesting depth of headings (`depth`) to horizontal offsets, ensuring a tree-like visual indentation. These offsets are derived using `getLineOffset` and `getItemOffset` functions, which transform the hierarchical document structure into a renderable SVG path.
| Function | Purpose | Input |
| :--- | :--- | :--- |
| `getLineOffset` | Returns horizontal starting position for lines | `depth: number` |
| `getItemOffset` | Returns horizontal padding for text items | `depth: number` |
Sources: [packages/base-ui/src/components/toc/default.tsx:240-250](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/toc/default.tsx#L240-L250)
## Context-Based Integration
The TOC subsystem relies on `React Context` to expose the observer and the scroll state to nested items. The `AnchorProvider` wraps the content, providing an `ObserverContext`, while the `ScrollProvider` provides the `containerRef`, necessary for the `scrollIntoView` logic when a user clicks an item.
```mermaid
sequenceDiagram
participant P as TOCProvider
participant O as AnchorProvider
participant I as TOCItem
P->>O: Provide TOC items
O->>O: Initialize Observer
O->>I: Provide Observer via Context
I->>I: Watch for intersection
I->>I: Trigger Auto-scroll on active
```
Sources: [packages/core/src/toc.tsx:38-83](https://github.com/blade47/fumadocs/blob/main/packages/core/src/toc.tsx#L38-L83)
## Static Data and MDX Integration
To support static generation or server-side rendering contexts (like `remark`), the subsystem includes plugins such as `remark-structure` and `rehype-toc`. These plugins parse the MDX tree to extract headings and their metadata *before* the component is even rendered on the client.
The `rehype-toc` plugin specifically scans heading tags and generates an array of items, which can then be exported as an ESM variable (`toc`) or embedded in the file's metadata. This ensures that the TOC doesn't require a full re-parse on every client render if the structure is known at build time.
> [!IMPORTANT]
> The `rehype-toc` plugin requires `hProperties.id` to be present on headings. If headings lack IDs, the TOC logic cannot reliably track them; `remark-heading` should be used as a prerequisite plugin to ensure every heading has a stable, slugified ID.
Sources: [packages/core/src/mdx-plugins/rehype-toc.ts:55-108](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/rehype-toc.ts#L55-L108)
## Design Trade-offs
The architecture reflects a preference for performance and visual flexibility over a single-component solution.
| Design Choice | Benefit | Cost |
| :--- | :--- | :--- |
| `IntersectionObserver` | High performance, avoids main-thread scroll jank | Requires consistent element IDs |
| SVG-based track lines | Fluid visual connections and step markers | Complexity in calculating SVG offsets |
| Reactive Context-based API | Decoupled UI and Logic, supports multiple layouts | Requires nested providers |
Sources: [packages/core/src/toc.tsx:228-346](https://github.com/blade47/fumadocs/blob/main/packages/core/src/toc.tsx#L228-L346)
## Implementation Example: Integrating the TOC
To use the TOC in a document layout, wrap the content in the `TOCProvider` and render the `TOC` component. The following snippet illustrates how a developer integrates the TOC into a layout:
```tsx
import { TOCProvider, TOC } from 'fumadocs-ui/components/toc';
export function DocsPage({ toc, children }) {
return (
{children}
);
}
```
Sources: [packages/preview/src/pages/[...slugs].tsx:212-221](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/pages/%5B...slugs%5D.tsx#L212-L221)
> [!TIP]
> Always place the `TOCProvider` as high as possible in the document component tree to ensure all `TOCItem` components receive the updated context immediately upon mount.
## Related
- [[Layout System]]
---
## Syntax Highlighting
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/syntax-highlighting
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/mdx/src/loaders/mdx/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/mdx/index.ts)
- [packages/core/src/mdx-plugins/transformer-icon.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/transformer-icon.ts)
- [packages/mdx/src/loaders/mdx/remark-include.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/mdx/remark-include.ts)
- [packages/mdx/src/node/_loader.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/node/_loader.ts)
- [packages/core/src/mdx-plugins/rehype-code.core.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/rehype-code.core.ts)
- [packages/core/src/highlight/client.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/highlight/client.ts)
- [packages/obsidian/src/mdx/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/mdx/index.ts)
- [packages/core/src/mdx-plugins/rehype-code/shiki.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/rehype-code/shiki.ts)
- [packages/core/src/highlight/shiki/react.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/highlight/shiki/react.ts)
- [packages/mdx/src/webpack/meta.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/webpack/meta.ts)
- [packages/base-ui/src/components/dynamic-codeblock.core.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/dynamic-codeblock.core.tsx)
- [packages/radix-ui/src/components/dynamic-codeblock.core.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/dynamic-codeblock.core.tsx)
- [packages/core/src/highlight/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/highlight/index.ts)
- [packages/mdx/src/loaders/meta.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/meta.ts)
- [packages/core/src/highlight/shiki/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/highlight/shiki/index.ts)
- [packages/core/src/mdx-plugins/remark-mdx-files.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-mdx-files.ts)
- [packages/base-ui/src/components/codeblock.rsc.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/codeblock.rsc.tsx)
- [packages/radix-ui/src/components/codeblock.rsc.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/codeblock.rsc.tsx)
- [packages/asyncapi/src/ui/components/codeblock.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/components/codeblock.tsx)
- [packages/openapi/src/ui/components/codeblock.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/components/codeblock.tsx)
- [packages/twoslash/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/src/index.ts)
- [packages/core/src/highlight/shiki/full.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/highlight/shiki/full.ts)
- [packages/core/src/mdx-plugins/rehype-code/parsers.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/rehype-code/parsers.ts)
- [packages/base-ui/src/components/codeblock.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/codeblock.tsx)
- [packages/radix-ui/src/components/codeblock.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/codeblock.tsx)
- [packages/base-ui/css/lib/shiki.css](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/css/lib/shiki.css)
- [packages/radix-ui/css/lib/shiki.css](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/css/lib/shiki.css)
- [packages/core/src/mdx-plugins/rehype-code.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/rehype-code.ts)
- [packages/mdx/src/loaders/config.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/config.ts)
- [packages/core/src/highlight/utils.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/highlight/utils.ts)
Syntax Highlighting in this system is powered by [Shiki](https://shiki.style), leveraging its high-performance tokenization capabilities to provide semantic code highlighting within MDX and React components. The system is designed to operate both as a static build-time transformation—converting code blocks into HTML structures during MDX compilation—and as a dynamic runtime client-side service for specialized components.
By decoupling the highlighter instance from the UI, the architecture allows for flexible theme management and on-demand language loading. The system handles the complexities of metadata parsing, line numbering, and icon injection through a pipeline of `rehype` plugins, ensuring that code blocks are rendered with proper styling, accessibility, and interactivity.
The system addresses the challenge of large bundle sizes by employing lazy-loading for languages and themes. When an unknown language or theme is requested, the system attempts to resolve and load it asynchronously. This ensures the initial bundle remains small while keeping the documentation flexible enough to support a vast array of programming languages and design tokens.
## Architecture and Pipeline Execution
The syntax highlighting system operates as a multi-stage pipeline, primarily integrated through `rehype`. During the build, the `rehype-code` plugin detects code nodes, parses meta-strings for configuration, and transforms them into syntax-highlighted HAST (HTML Abstract Syntax Tree) structures.
```mermaid
flowchart TD
A["Raw MDX Code Block"] --> B["Rehype Plugin Pipeline"]
B --> C["Parse Meta String"]
C --> D["Identify Language"]
D --> E["Load Theme/Language
(On-demand)"]
E --> F["Apply Transformers
(Icon, Tab, Twoslash)"]
F --> G["Render to HAST"]
G --> H["Inject to UI"]
```
Sources: [packages/core/src/mdx-plugins/rehype-code.ts:19-34](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/rehype-code.ts#L19-L34), [packages/core/src/mdx-plugins/rehype-code.core.ts:82-142](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/rehype-code.core.ts#L82-L142)
## Shiki Instance Factory and Lifecycle
The `ShikiFactory` provides a mechanism for initializing and accessing the highlighter instance. It ensures that expensive initialization only occurs once, supporting lazy-loading of engines (either `js` or `oniguruma` via WASM).
> [!TIP]
> The `getOrInit` method ensures that the highlighter instance is reused across the entire application lifecycle, preventing redundant loading of the heavy tokenization engine.
Sources: [packages/core/src/highlight/shiki/index.ts:20-36](https://github.com/blade47/fumadocs/blob/main/packages/core/src/highlight/shiki/index.ts#L20-L36)
## Meta String Parsing and Configuration
Meta strings inside code blocks (e.g., `ts {1,3} title="example.ts"`) are processed by custom parsers. The core logic extracts data attributes like titles, line numbers, and custom metadata, transforming them into a structured data object used by the Shiki pipeline.
The `parseMetaString` function allows users to define custom transformations. For example, `lineNumbers=5` is stripped from the meta string and converted into the `data-line-numbers-start` attribute.
Sources: [packages/core/src/mdx-plugins/rehype-code.core.ts:37-56](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/rehype-code.core.ts#L37-L56)
## Dynamic Code Execution Flow
For interactive or documentation-heavy UI components (like API Docs or OpenApi), the system uses dynamic highlighting at runtime. This avoids shipping large bundles to the browser while maintaining high-fidelity code display.
```mermaid
sequenceDiagram
participant UI as DynamicCodeBlock
participant SH as Shiki Instance
participant Hook as useShikiDynamic
UI->>Hook: Request highlight(lang, code)
Hook->>SH: Ensure language/theme loaded
SH-->>Hook: Instance Ready
Hook->>SH: codeToHast(code, options)
SH-->>Hook: HAST Fragments
Hook->>UI: Render HAST to React Components
```
Sources: [packages/core/src/highlight/shiki/react.ts:27-59](https://github.com/blade47/fumadocs/blob/main/packages/core/src/highlight/shiki/react.ts#L27-L59), [packages/base-ui/src/components/dynamic-codeblock.core.tsx:45-67](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/dynamic-codeblock.core.tsx#L45-L67)
## Language and Theme Resolution
To keep the system performant, `utils.ts` contains logic to check if languages and themes are already registered in the current `HighlighterCore` instance. If a language or theme is requested but not present, the `loadMissingLanguage` and `loadMissingTheme` helpers are triggered to fetch them asynchronously before execution.
| Function | Purpose |
| :--- | :--- |
| `loadMissingTheme` | Dynamically loads a theme registration if not in `bundledThemes`. |
| `loadMissingLanguage` | Dynamically loads a language registration if not in `bundledLanguages`. |
| `getRequiredThemes` | Resolves standard theme sets from the configuration. |
Sources: [packages/core/src/highlight/utils.ts:9-55](https://github.com/blade47/fumadocs/blob/main/packages/core/src/highlight/utils.ts#L9-L55)
## Transformer Extension Points
Shiki transformers are used to manipulate the AST before serialization. The system includes pre-defined transformers for notation diffing, focus, and word highlighting, along with custom extensions like icon injection.
> [!WARNING]
> Transformers are executed sequentially. The order in which they are added to the `transformers` array is critical, especially when modifying node structures.
Sources: [packages/core/src/mdx-plugins/rehype-code.core.ts:107-115](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/rehype-code.core.ts#L107-L115)
## Practical Implementation Example
To implement a syntax-highlighted code block in a React component, use the `DynamicCodeBlock` provided by the base UI package:
```tsx
import { DynamicCodeBlock } from 'fumadocs-ui/components/dynamic-codeblock.core';
import { getHighlighter } from 'fumadocs-core/highlight';
export default function MyPage() {
return (
getHighlighter('js')}
lang="typescript"
code="const greeting: string = 'Hello World';"
options={{ theme: { light: 'github-light', dark: 'github-dark' } }}
/>
);
}
```
Sources: [packages/base-ui/src/components/dynamic-codeblock.core.tsx:45-67](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/dynamic-codeblock.core.tsx#L45-L67)
## Related
- [[Twoslash Highlight]]
- [[Base Components]]
---
## Base Components
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/base-components
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/radix-ui/src/components/sidebar/base.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/sidebar/base.tsx)
- [packages/base-ui/src/components/sidebar/base.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/sidebar/base.tsx)
- [packages/base-ui/package.json](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/package.json)
- [packages/base-ui/src/components/tabs.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/tabs.tsx)
- [packages/base-ui/src/layouts/notebook/slots/sidebar.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/notebook/slots/sidebar.tsx)
- [packages/radix-ui/src/layouts/notebook/slots/sidebar.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/layouts/notebook/slots/sidebar.tsx)
- [packages/sanity/registry/base.component.tsx](https://github.com/blade47/fumadocs/blob/main/packages/sanity/registry/base.component.tsx)
- [packages/api-docs/src/components/accordion.tsx](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/components/accordion.tsx)
- [packages/base-ui/src/components/image-zoom.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/image-zoom.tsx)
- [packages/base-ui/src/components/codeblock.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/codeblock.tsx)
- [packages/base-ui/src/mdx.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/mdx.tsx)
- [packages/radix-ui/src/components/image-zoom.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/image-zoom.tsx)
- [packages/radix-ui/src/components/codeblock.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/codeblock.tsx)
- [packages/base-ui/src/components/accordion.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/accordion.tsx)
- [packages/base-ui/src/components/callout.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/callout.tsx)
- [packages/radix-ui/src/mdx.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/mdx.tsx)
- [packages/radix-ui/src/components/accordion.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/accordion.tsx)
- [packages/sanity/registry/accordion.component.tsx](https://github.com/blade47/fumadocs/blob/main/packages/sanity/registry/accordion.component.tsx)
- [packages/base-ui/src/components/ui/tabs.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/ui/tabs.tsx)
- [packages/radix-ui/src/components/callout.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/callout.tsx)
- [packages/sanity/registry/steps.component.tsx](https://github.com/blade47/fumadocs/blob/main/packages/sanity/registry/steps.component.tsx)
- [packages/sanity/registry/tabs.component.tsx](https://github.com/blade47/fumadocs/blob/main/packages/sanity/registry/tabs.component.tsx)
- [packages/openapi/src/ui/base.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/base.tsx)
- [packages/sanity/registry/files.component.tsx](https://github.com/blade47/fumadocs/blob/main/packages/sanity/registry/files.component.tsx)
- [packages/python/src/components/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/python/src/components/index.tsx)
- [packages/base-ui/registry/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/registry/index.ts)
- [packages/base-ui/src/components/ui/accordion.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/ui/accordion.tsx)
- [packages/core/src/mdx-plugins/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/index.ts)
- [packages/base-ui/src/components/steps.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/steps.tsx)
- [packages/radix-ui/registry/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/registry/index.ts)
Base Components constitute the foundational UI building blocks in the Fumadocs architecture, designed to provide a consistent, reusable, and accessible interface for documentation sites. These components abstract low-level primitives into domain-specific modules like sidebars, code blocks, and interactive tabs, ensuring that functionality remains decoupled from specific layout implementations. By standardizing these components, the system facilitates rapid development of documentation layouts while maintaining a high degree of customizability.
At the core of these components is a focus on "mechanism over style." Many components rely on context providers (`SidebarContext`, `TabsContext`, `FolderContext`) to share state across deep component hierarchies, allowing for behaviors like automatic scrolling, shared keyboard navigation, and synced UI states. This architecture enables developers to compose complex documentation features (such as collapsible navigation trees) without manual state management.
The system is split into two primary implementation tiers: `base-ui` and `radix-ui`. While they share identical external APIs and shared logic, the `radix-ui` variant leverages Radix UI primitives, providing a different set of underlying behaviors and accessibility features compared to the standard `base-ui` implementation. This duality ensures that Fumadocs can support diverse technical requirements while maintaining a unified interface for the developer.
## Sidebar Architecture and State Management
The sidebar system is a complex subsystem composed of providers and specialized slots. It manages navigation state, responsive behaviors (drawer vs. full-width), and folder expansion. The state is maintained in `SidebarContext` and `FolderContext`, allowing nested components like `SidebarFolder`, `SidebarItem`, and `SidebarTrigger` to react to global changes in the sidebar state.
The core mechanism for responsiveness relies on `useMediaQuery` to toggle between `drawer` and `full` modes. In drawer mode, the sidebar uses an overlay element managed by `Presence` to handle animations.
```mermaid
flowchart TD
A["SidebarProvider"] --> B["SidebarContext (Global State)"]
B --> C["SidebarContent"]
B --> D["SidebarDrawerOverlay"]
D --> E["Presence (Animation)"]
F["SidebarFolder"] --> G["FolderContext (Hierarchical Depth)"]
G --> H["SidebarFolderTrigger"]
G --> I["SidebarFolderContent"]
I --> J["SidebarFolderLink"]
```
Sources: [packages/radix-ui/src/components/sidebar/base.tsx:32-45](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/sidebar/base.tsx#L32-L45), [packages/radix-ui/src/components/sidebar/base.tsx:75-112](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/sidebar/base.tsx#L75-L112)
> [!TIP]
> Use the `defaultOpenLevel` prop in `SidebarProvider` to control the initial expansion state of folders. Folders with a depth less than or equal to this level will automatically initialize as expanded.
## Tab Collection Logic
The `Tabs` component implements a robust collection mechanism inspired by Headless UI to handle child order and selection. Instead of relying on manual index management, each `Tab` component uses a unique ID (`useId`) and registers itself into a shared `collection` array held in `TabsContext`.
When `Tab` renders, it calls `useCollectionIndex()` which performs the following logic:
1. Generates a unique ID for the instance.
2. Checks if the ID exists in the collection.
3. If not, pushes it to the collection.
4. Returns the index of the ID within the collection array.
Sources: [packages/base-ui/src/components/tabs.tsx:184-197](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/tabs.tsx#L184-L197)
## Code Block Mechanism and Copy Logic
The `CodeBlock` component handles code highlighting visualization with built-in actions like copying. It leverages `useCopyButton` to orchestrate clipboard interactions. A significant detail in the copying mechanism is the handling of non-copyable elements via `cloneNode`:
- The `containerRef` is used to retrieve the inner `re>` tag.
- `cloneNode(true)` creates a deep clone of the content to avoid modifying the visual DOM.
- The clone traverses its own children to remove nodes marked with the `.nd-copy-ignore` class before executing `navigator.clipboard.writeText`.
```typescript
const [checked, onClick] = useCopyButton(() => {
const pre = containerRef.current?.getElementsByTagName('pre').item(0);
if (!pre) return;
const clone = pre.cloneNode(true) as HTMLElement;
clone.querySelectorAll('.nd-copy-ignore').forEach((node) => {
node.replaceWith('\n');
});
void navigator.clipboard.writeText(clone.textContent ?? '');
});
```
Sources: [packages/base-ui/src/components/codeblock.tsx:157-167](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/codeblock.tsx#L157-L167)
## Accordion Anchor Integration
The `Accordion` component integrates with the `auto-anchor` system to support deep-linking directly to specific accordion sections. This is implemented via a `useEffect` inside `AccordionItem` that monitors the URL hash and automatically opens the corresponding accordion section if it matches the generated `id`.
The `anchorIdStartsWith` guard is crucial here: it verifies if the hash segment matches the section's ID, preventing arbitrary hash triggers from opening unrelated accordions.
Sources: [packages/api-docs/src/components/accordion.tsx:43-46](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/components/accordion.tsx#L43-L46)
## Sidebar Auto-Scroll Logic
The sidebar uses a custom `useAutoScroll` hook to keep active items in view within the viewport. This is triggered whenever an item becomes `active`. The mechanism calculates the boundary of the sidebar based on its current mode:
- **Drawer Mode:** Targets the element with ID `nd-sidebar-mobile`.
- **Full Mode:** Targets the element with ID `nd-sidebar`.
The `scrollIntoView` library is invoked with `{ scrollMode: 'if-needed' }` to ensure the sidebar does not jitter or force-scroll if the item is already visible.
Sources: [packages/radix-ui/src/components/sidebar/base.tsx:402-410](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/sidebar/base.tsx#L402-L410)
## Callout Variant Resolution
Callouts support multiple types, but the implementation allows for aliasing to maintain backwards compatibility or semantic clarity. The `resolveAlias` function handles these mappings before the visual container is rendered:
| Alias | Resolved Value |
| :--- | :--- |
| `warn` | `warning` |
| `tip` | `info` |
The resolve logic ensures that the internal CSS variable `--callout-color` correctly maps to the corresponding theme variable (e.g., `--color-fd-info` or `--color-fd-warning`).
Sources: [packages/base-ui/src/components/callout.tsx:34-38](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/callout.tsx#L34-L38)
> [!CAUTION]
> Manually overriding `style` on a `CalloutContainer` may break the automatic `--callout-color` calculation. If custom styling is required, use the `className` prop to target the container directly rather than overriding the internal CSS variables.
## Related
- [[Syntax Highlighting]]
---
## Twoslash Highlight
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/twoslash-highlight
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/twoslash/styles/twoslash.css](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/styles/twoslash.css)
- [packages/api-docs/src/components/schema/client.tsx](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/components/schema/client.tsx)
- [packages/core/src/toc.tsx](https://github.com/blade47/fumadocs/blob/main/packages/core/src/toc.tsx)
- [packages/base-ui/src/components/toc/clerk.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/toc/clerk.tsx)
- [packages/twoslash/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/src/index.ts)
- [packages/twoslash/.oxlintrc.json](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/.oxlintrc.json)
- [packages/story/src/type-tree/builder.ts](https://github.com/blade47/fumadocs/blob/main/packages/story/src/type-tree/builder.ts)
- [packages/base-ui/src/layouts/notebook/page/slots/toc.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/notebook/page/slots/toc.tsx)
- [packages/base-ui/src/layouts/shared/slots/language-select.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/shared/slots/language-select.tsx)
- [packages/base-ui/src/components/dynamic-codeblock.core.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/dynamic-codeblock.core.tsx)
- [packages/radix-ui/src/components/dynamic-codeblock.core.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/dynamic-codeblock.core.tsx)
- [packages/twoslash/src/ui/popup.tsx](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/src/ui/popup.tsx)
- [packages/core/src/mdx-plugins/rehype-code/shiki.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/rehype-code/shiki.ts)
- [packages/core/src/highlight/shiki/react.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/highlight/shiki/react.ts)
- [packages/twoslash/src/ui/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/src/ui/index.ts)
- [packages/base-ui/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/tsdown.config.ts)
- [packages/base-ui/src/components/codeblock.rsc.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/codeblock.rsc.tsx)
- [packages/radix-ui/src/components/codeblock.rsc.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/codeblock.rsc.tsx)
- [packages/base-ui/src/layouts/flux/page/slots/toc.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/layouts/flux/page/slots/toc.tsx)
- [packages/core/src/highlight/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/highlight/index.ts)
- [packages/twoslash/src/cache-fs.ts](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/src/cache-fs.ts)
- [packages/core/src/mdx-plugins/remark-llms.runtime.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-llms.runtime.ts)
- [packages/twoslash/package.json](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/package.json)
- [packages/preview/src/components/markdown.tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/components/markdown.tsx)
- [packages/python/src/components/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/python/src/components/index.tsx)
- [packages/twoslash/tsconfig.json](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/tsconfig.json)
- [packages/twoslash/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/tsdown.config.ts)
- [packages/typescript/src/cache/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/typescript/src/cache/index.ts)
- [packages/typescript/src/markdown.ts](https://github.com/blade47/fumadocs/blob/main/packages/typescript/src/markdown.ts)
- [packages/twoslash/src/ui/cn.ts](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/src/ui/cn.ts)
Twoslash Highlight is a sophisticated documentation enrichment subsystem that bridges the gap between static code samples and interactive TypeScript analysis. By integrating the `twoslash` engine directly into the documentation rendering pipeline, it enables features like hover-based type information, automatic error highlighting, and code completion previews. It is designed to work in conjunction with the Fumadocs `rehype-code` pipeline, transforming standard markdown code blocks into dynamic, information-rich UI elements.
The architecture centers on a Shiki-based transformer factory. When a documentation page is processed, Twoslash interprets special comments within code blocks to perform TypeScript language service operations. The resultant data—containing type definitions, diagnostics, and metadata—is then mapped into HTML structures (via Shiki's renderer) that React components (like the custom `Popup`) use to present interactive overlays.
A key design decision in this subsystem is the "lazy-loaded" instance pattern. By delaying the initialization of the heavy `twoslash` engine until requested, the system ensures that build times remain performant and serverless environments remain lightweight. This implementation detail prevents unnecessary instantiation while allowing the system to scale to large documentation sets containing thousands of code snippets.
## The Transformer Factory Mechanism
The transformer is orchestrated by the `transformerTwoslash` function, which serves as the entry point for Fumadocs code block processing. This function leverages `createTransformerFactory` to bridge the gap between static Shiki highlighting and dynamic Twoslash analysis.
```mermaid
flowchart TD
A["rehype-code plugin"] --> B["transformerTwoslash"]
B --> C["lazyInstance()"]
C --> D["createTwoslasher"]
B --> E["rendererRich"]
E --> F["hast:hoverToken"]
E --> G["hast:hoverPopup"]
```
Sources: [packages/twoslash/src/index.ts:31-131](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/src/index.ts#L31-L131)
The mechanism for lazy loading is crucial for resource management. The `lazyInstance` function uses a cached singleton `cachedInstance`. If the cache is empty, it invokes `createTwoslasher`, configuring it with a `Bundler` module resolution strategy. This ensures that the TypeScript compiler options are correctly set for modern documentation environments.
Sources: [packages/twoslash/src/index.ts:25-52](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/src/index.ts#L25-L52)
> [!NOTE]
> The `compilerOptions` override sets `moduleResolution` to `100` (Bundler). This is a rigid invariant to ensure that `twoslash` accurately resolves imports that are standard in modern frontend documentation projects, regardless of the user's specific `tsconfig.json` settings.
## Rendering Pipeline and HAST Transformation
The transformation process relies on `rendererRich` from `@shikijs/twoslash`. It defines how Twoslash metadata is converted into HAST (Hypertext Abstract Syntax Tree) nodes that Fumadocs renders.
The core mapping logic intercepts standard HAST tokens and maps them to HAST properties configured via the renderer's `hast` option.
Sources: [packages/twoslash/src/index.ts:54-108](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/src/index.ts#L54-L108)
For line-specific queries (such as diagnostics or errors), the code performs an explicit HAST node modification:
```typescript
const fn = renderer.lineQuery!;
renderer.lineQuery = function (this: ShikiTransformerContext, ...args) {
const result = fn.call(this, ...args);
// Re-wrap to ensure proper styling as a span
const child = result[0].children[0];
result[0].children[0] = {
type: 'element',
tagName: 'span',
children: [child],
};
return result;
};
```
Sources: [packages/twoslash/src/index.ts:110-123](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/src/index.ts#L110-L123)
## Interactive Popup Component Lifecycle
The interaction layer is defined in `packages/twoslash/src/ui/popup.tsx`. It provides a context-aware `Popup` component that manages hover-state timing with `setTimeout` to prevent UI jitter.
```mermaid
sequenceDiagram
participant User
participant PopupTrigger
participant Popup
participant PopoverContent
User->>PopupTrigger: pointerEnter
Popup->>Popup: set timer (delay 300ms)
alt Time elapsed
Popup->>PopoverContent: setOpen(true)
end
User->>PopupTrigger: pointerLeave
Popup->>Popup: clear timers, setOpen(false)
```
Sources: [packages/twoslash/src/ui/popup.tsx:1-61](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/src/ui/popup.tsx#L1-L61)
The system uses pointer events (`onPointerEnter`, `onPointerLeave`) rather than simple `onMouseEnter` to better handle different input devices, specifically ignoring `touch` events in the implementation logic to prevent unintentional interactions on mobile devices.
Sources: [packages/twoslash/src/ui/popup.tsx:38-46](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/src/ui/popup.tsx#L38-L46)
## Caching Strategy
Performance is maintained via a file-system-based cache for type checking results. This is critical because `twoslash` runs the TypeScript language server on code snippets, which is computationally expensive.
The `createFileSystemTypesCache` provides an `init`, `read`, and `write` interface:
1. **Hash Generation:** It uses `SHA256` to create a 12-character identifier based on the source code string.
2. **Persistence:** Files are stored in `.next/cache/twoslash` by default, allowing subsequent builds or server restarts to bypass expensive type re-analysis.
Sources: [packages/twoslash/src/cache-fs.ts:1-50](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/src/cache-fs.ts#L1-L50)
> [!WARNING]
> While `read` and `write` are synchronous, ensure that the cache directory has appropriate write permissions. Failure to write will not crash the compilation but will significantly degrade documentation build performance.
## Styling System
The subsystem uses CSS custom properties and scoped classes to integrate into Fumadocs' theme system. The styles are defined in `twoslash.css`.
| Class Name | Purpose | Key Styling |
| :--- | :--- | :--- |
| `.fd-twoslash-popover` | Root popover container | `z-index: 50`, `border-radius: xl` |
| `.twoslash-error` | Wavy diagnostic underline | `text-decoration: wavy underline` |
| `.twoslash-tag-line` | Custom tag blocks | `border-left: 3px solid` |
Sources: [packages/twoslash/styles/twoslash.css:199-225](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/styles/twoslash.css#L199-L225)
The use of `currentColor` for hover transitions ensures that the interactive UI indicators inherit the document's theme color automatically, preventing manual synchronization overhead in the theme configuration.
Sources: [packages/twoslash/styles/twoslash.css:88-101](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/styles/twoslash.css#L88-L101)
## Implementation Pattern
To integrate Twoslash in a documentation project, developers rely on the transformer export.
```typescript
import { transformerTwoslash } from 'fumadocs-twoslash';
// Inside your shiki configuration
const transformer = transformerTwoslash({
twoslashOptions: {
// Add custom compiler options here
}
});
```
Sources: [packages/twoslash/src/index.ts:31-45](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/src/index.ts#L31-L45)
The renderer is inherently decoupled, meaning you can extend the `hast` configuration if you need to add custom UI triggers or tooltips beyond the standard `Popup` component, as shown in the renderer setup in the index source file.
Sources: [packages/twoslash/src/index.ts:106-107](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/src/index.ts#L106-L107)
## Related
- [[Syntax Highlighting]]
- [[TypeScript Tables]]
---
## TypeScript Tables
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/typescript-tables
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/typescript/src/ui/auto-type-table.tsx](https://github.com/blade47/fumadocs/blob/main/packages/typescript/src/ui/auto-type-table.tsx)
- [packages/typescript/src/lib/remark-auto-type-table.ts](https://github.com/blade47/fumadocs/blob/main/packages/typescript/src/lib/remark-auto-type-table.ts)
- [packages/story/src/type-tree/builder.ts](https://github.com/blade47/fumadocs/blob/main/packages/story/src/type-tree/builder.ts)
- [packages/core/src/mdx-plugins/remark-structure.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-structure.ts)
- [packages/typescript/src/lib/base.ts](https://github.com/blade47/fumadocs/blob/main/packages/typescript/src/lib/base.ts)
- [packages/core/src/content/md.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/content/md.ts)
- [packages/local-md/src/md/renderer.ts](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/md/renderer.ts)
- [packages/mdx/src/runtime/server.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/server.ts)
- [packages/mdx/src/loaders/mdx/remark-include.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/mdx/remark-include.ts)
- [packages/doc-gen/src/remark-ts2js.ts](https://github.com/blade47/fumadocs/blob/main/packages/doc-gen/src/remark-ts2js.ts)
- [packages/api-docs/src/components/schema/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/components/schema/index.tsx)
- [packages/core/src/source/llms.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/llms.ts)
- [packages/typescript/src/lib/type-table.ts](https://github.com/blade47/fumadocs/blob/main/packages/typescript/src/lib/type-table.ts)
- [packages/python/src/convert.ts](https://github.com/blade47/fumadocs/blob/main/packages/python/src/convert.ts)
- [packages/api-docs/src/schema/to-string.ts](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/schema/to-string.ts)
- [packages/twoslash/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/src/index.ts)
- [packages/typescript/src/markdown.ts](https://github.com/blade47/fumadocs/blob/main/packages/typescript/src/markdown.ts)
- [packages/base-ui/src/components/type-table.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/type-table.tsx)
- [packages/typescript/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/typescript/src/index.ts)
- [packages/radix-ui/src/components/type-table.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/type-table.tsx)
- [packages/openapi/src/utils/pages/to-static-data.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/pages/to-static-data.ts)
- [packages/content-collections/src/configuration.ts](https://github.com/blade47/fumadocs/blob/main/packages/content-collections/src/configuration.ts)
- [packages/story/src/utils/generate.ts](https://github.com/blade47/fumadocs/blob/main/packages/story/src/utils/generate.ts)
- [packages/typescript/src/ui/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/typescript/src/ui/index.ts)
- [packages/typescript/src/lib/parse-tags.ts](https://github.com/blade47/fumadocs/blob/main/packages/typescript/src/lib/parse-tags.ts)
- [packages/api-docs/src/schema/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/schema/index.ts)
- [packages/core/src/source/source.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/source.ts)
- [packages/story/src/type-tree/types.ts](https://github.com/blade47/fumadocs/blob/main/packages/story/src/type-tree/types.ts)
- [packages/python/src/generated.ts](https://github.com/blade47/fumadocs/blob/main/packages/python/src/generated.ts)
- [packages/doc-gen/src/remark-docgen.ts](https://github.com/blade47/fumadocs/blob/main/packages/doc-gen/src/remark-docgen.ts)
"TypeScript Tables" in Fumadocs serve as an automated documentation bridge between TypeScript source code and human-readable web documentation. By utilizing static analysis of source files, the system extracts type signatures, JSDoc comments, and structural metadata to render interactive, searchable tables directly into documentation pages. This eliminates the manual maintenance of API documentation, ensuring that public interfaces are always aligned with the actual implementation.
The subsystem operates through a multi-stage pipeline: a "Generator" (built on `ts-morph`) extracts the AST of target types, which is then processed into a generic intermediate format. This format is then handed off to React components (via `AutoTypeTable` or Remark plugins) to produce styled interfaces. The architecture is modular, allowing users to embed tables through React components or declaratively within Markdown files using a specialized MDX directive, essentially treating source code as the primary source of truth for documentation content.
The system addresses the "doc-sync" problem by embedding documentation generation into the build pipeline. It treats TypeScript definitions as data, allowing for recursive type expansion, union unwrap-strategies, and sophisticated comment parsing. By integrating closely with `unified` and `mdast`, the system provides a seamless experience for authors who want to document complex TypeScript APIs without leaving their Markdown documents.
## Core Generation Pipeline
The process begins with the `Generator` interface, which wraps `ts-morph`. When `generateDocumentation` is called, it identifies an export in a source file, determines the type, and recursively traverses the properties to build a `GeneratedDoc` object.
```mermaid
flowchart TD
A["File Source (ts-morph)"] --> B["getExportedDeclarations"]
B --> C["getType (ts-morph)"]
C --> D["getDocEntry"]
D --> E["Extract Tags/Comments"]
E --> F["Generate GeneratedDoc"]
```
Sources: [packages/typescript/src/lib/base.ts:137-190](https://github.com/blade47/fumadocs/blob/main/packages/typescript/src/lib/base.ts#L137-L190)
## The `remark-auto-type-table` Plugin
The Remark plugin translates Markdown directives (like ``) into `mdxJsxFlowElement` nodes containing the actual generated UI code. It is designed to work within MDX pipelines, enabling authors to place these tables anywhere in their content.
1. **Visitor Traversal**: `visit(tree, 'mdxJsxFlowElement', ...)` scans the MDX content for the `auto-type-table` tag name.
2. **Path Resolution**: It uses the file context (`file.dirname` or explicit `cwd`) to locate the source TypeScript file.
3. **Execution**: The `generate` function invokes the generator and creates an `estree` representation of the table props, ensuring that the React component receives typed data.
Sources: [packages/typescript/src/lib/remark-auto-type-table.ts:231-282](https://github.com/blade47/fumadocs/blob/main/packages/typescript/src/lib/remark-auto-type-table.ts#L231-L282)
## Type Tree Builders and Handlers
The "Type Tree" logic is responsible for converting `ts-morph` types into a serializable `TypeNode` object. It employs a chain-of-responsibility pattern where multiple handlers attempt to transform a type, with the `baseHandler` serving as the fallback.
> [!TIP]
> The `createTypeTreeBuilder` function accepts a `customHandlers` array. These handlers are processed in order; place specific handlers (like `literalEnumHandler`) before the `baseHandler` to override default behavior.
Sources: [packages/story/src/type-tree/builder.ts:233-248](https://github.com/blade47/fumadocs/blob/main/packages/story/src/type-tree/builder.ts#L233-L248)
| Design Choice | Benefit | Cost |
| :--- | :--- | :--- |
| Recursive Traversal | Handles nested objects and interfaces naturally | Susceptible to stack overflow with circular types |
| WeakMap Caching | Prevents redundant parsing of large type graphs | Higher memory usage during build |
| AST-based Extraction | Type-safe extraction independent of runtime | Slower build performance due to full TS analysis |
Sources: [packages/story/src/type-tree/builder.ts:243-264](https://github.com/blade47/fumadocs/blob/main/packages/story/src/type-tree/builder.ts#L243-L264)
## JSDoc and Tag Parsing
The subsystem parses tags from JSDoc comments to enhance the displayed metadata, specifically looking for `default`, `param`, and `returns` tags. The `parseTags` function is the primary utility for converting raw tag data into a structured `TypedTags` object.
> [!CAUTION]
> Tags are parsed using a simple string separator index; a missing separator `-` in a `@param` description results in the entire text being assigned to the parameter name, leaving the description empty.
Sources: [packages/typescript/src/lib/parse-tags.ts:17-45](https://github.com/blade47/fumadocs/blob/main/packages/typescript/src/lib/parse-tags.ts#L17-L45)
## UI Components and Rendering
The `TypeTable` component provides the visual representation. It is designed for reusability, supporting both standard and Radix-themed interfaces. It handles collapsible entries, allowing users to inspect complex properties without cluttering the documentation view.
```tsx
// Example usage: Rendering a table manually
Description here
,
required: true
}
}}
/>
```
Sources: [packages/base-ui/src/components/type-table.tsx:52-78](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/type-table.tsx#L52-L78)
## Error Handling and Lifecycle
Errors are handled at the plugin level by tracking the source of the `mdxJsxFlowElement`. If the generator fails, the `onError` utility provides the file path and line number, preventing the build from failing silently and making debugging significantly easier in large docs repositories.
Sources: [packages/typescript/src/lib/remark-auto-type-table.ts:236-242](https://github.com/blade47/fumadocs/blob/main/packages/typescript/src/lib/remark-auto-type-table.ts#L236-L242)
> [!NOTE]
> The `AutoTypeTable` React component defaults to an empty object if no entries are returned; ensure the source path and exported name are accurate to avoid runtime discrepancies.
## Related
- [[Twoslash Highlight]]
---
## Command Line Interface
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/command-line-interface
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/create-app/src/bin.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/bin.ts)
- [packages/cli/src/commands/customise.ts](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/commands/customise.ts)
- [packages/cli/src/commands/add.ts](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/commands/add.ts)
- [packages/create-app/src/plugins/ai.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/plugins/ai.ts)
- [packages/create-app/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/index.ts)
- [packages/openapi/package.json](https://github.com/blade47/fumadocs/blob/main/packages/openapi/package.json)
- [packages/api-docs/package.json](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/package.json)
- [packages/local-md/package.json](https://github.com/blade47/fumadocs/blob/main/packages/local-md/package.json)
- [packages/cli/src/registry/installer.ts](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/registry/installer.ts)
- [packages/cli/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/index.ts)
- [packages/cli/package.json](https://github.com/blade47/fumadocs/blob/main/packages/cli/package.json)
- [packages/preview/src/cli/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/cli/index.ts)
- [packages/create-app/package.json](https://github.com/blade47/fumadocs/blob/main/packages/create-app/package.json)
- [packages/doc-gen/src/remark-install.ts](https://github.com/blade47/fumadocs/blob/main/packages/doc-gen/src/remark-install.ts)
- [packages/cli/src/commands/shared.ts](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/commands/shared.ts)
- [packages/preview/package.json](https://github.com/blade47/fumadocs/blob/main/packages/preview/package.json)
- [packages/create-app/scripts/sync.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/scripts/sync.ts)
- [packages/cli/src/config.ts](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/config.ts)
- [packages/python/fumapy/__init__.py](https://github.com/blade47/fumadocs/blob/main/packages/python/fumapy/__init__.py)
- [packages/cli/src/registry/plugins/preserve.ts](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/registry/plugins/preserve.ts)
- [packages/python/package.json](https://github.com/blade47/fumadocs/blob/main/packages/python/package.json)
- [packages/python/pyproject.toml](https://github.com/blade47/fumadocs/blob/main/packages/python/pyproject.toml)
- [packages/cli/src/commands/file-tree.ts](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/commands/file-tree.ts)
- [packages/radix-ui/package.json](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/package.json)
- [packages/asyncapi/package.json](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/package.json)
- [packages/base-ui/package.json](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/package.json)
- [packages/create-app-versions/package.json](https://github.com/blade47/fumadocs/blob/main/packages/create-app-versions/package.json)
- [packages/preview/src/styles/globals.css](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/styles/globals.css)
- [packages/twoslash/package.json](https://github.com/blade47/fumadocs/blob/main/packages/twoslash/package.json)
- [packages/create-app/src/constants.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/constants.ts)
The Fumadocs Command Line Interface (CLI) serves as the primary orchestration layer for initializing, configuring, and extending documentation projects. It solves the fragmentation of project scaffolding by providing a unified, interactive entry point that handles framework-specific requirements—such as template selection, dependency installation, and directory structure setup—without requiring manual boilerplate configuration.
Architecturally, the CLI is split into specialized packages: `create-app` for initial bootstrapping and `@fumadocs/cli` for ongoing project maintenance. It utilizes a plugin-based system to handle modular concerns like AI integration, linting configurations, and layout customization, ensuring that the CLI remains maintainable while supporting diverse frontend ecosystems (Next.js, Waku, React Router, and Tanstack Start).
Beyond scaffolding, the CLI provides deep operational utilities for managing UI components. It interacts with registries to fetch, install, and update UI components into a local project while strictly respecting user configuration defined in `cli.json`. By providing interactive prompts, the CLI abstracts complex configuration choices into a coherent user journey, ensuring consistent project standards across the Fumadocs ecosystem.
## CLI Architecture and Control Flow
The CLI operates by parsing user inputs via `commander`, which then dispatches tasks to specific command modules. The core logic for project management is decentralized into specialized commands like `add` and `customise`.
```mermaid
flowchart TD
A[User CLI Entry] --> B{Command Dispatched}
B -->|add| C[Install Component]
B -->|customise| D[Layout Configuration]
B -->|create| E[Scaffold Project]
C --> F[FumadocsComponentInstaller]
D --> F
E --> G[Template Plugin Pipeline]
F --> H[File System Operations]
G --> H
```
Sources: [packages/cli/src/index.ts:15-116](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/index.ts#L15-L116), [packages/create-app/src/bin.ts:56-300](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/bin.ts#L56-L300)
## Component Installation
The `FumadocsComponentInstaller` class encapsulates the logic for adding components to an existing project. It acts as the bridge between the remote component source and the local file system.
The installer uses a plugin architecture to preserve critical layout components. During installation, the `pluginPreserveLayouts` plugin acts as a safety gate.
```typescript
// Installer configuration logic in packages/cli/src/registry/installer.ts
this.interactive = { name, spin };
const deps = await super.install(name, subRegistry).then((res) => res.deps());
spin.stop(picocolors.bold(picocolors.greenBright(`${name} installed`)));
```
Sources: [packages/cli/src/registry/installer.ts:54-65](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/registry/installer.ts#L54-L65)
> [!WARNING]
> The installer enforces a strict state invariant: it throws an error if a new installation attempt occurs while an installation is already in progress (`if (this.interactive) throw new Error(...)`). This prevents race conditions in file system modifications.
Sources: [packages/cli/src/registry/installer.ts:55-57](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/registry/installer.ts#L55-L57)
## Configuration Lifecycle
The CLI maintains configuration via `cli.json`. The `createOrLoadConfig` function is the primary source of truth, performing a check to see if a file exists before parsing.
| Property | Default | Description |
| :--- | :--- | :--- |
| `uiLibrary` | `radix-ui` | The underlying UI registry used for components. |
| `framework` | Detected | Current project framework (e.g., `next`, `waku`). |
| `baseDir` | `src` or `app` | The base directory for source code. |
Sources: [packages/cli/src/config.ts:62-70](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/config.ts#L62-L70), [packages/cli/src/config.ts:14-55](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/config.ts#L14-L55)
## Plugin Pipeline in `create-app`
The project generator uses a sequential plugin pipeline to transform the initial template. Plugins can define hooks at different stages of the lifecycle.
```mermaid
sequenceDiagram
participant User
participant Plugin
participant Template
User->>Template: select template
Template->>Plugin: call template() hook
Plugin-->>Template: return modified info
Template->>Template: copy files
Template->>Plugin: call afterWrite() hook
```
Sources: [packages/create-app/src/index.ts:64-126](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/index.ts#L64-L126)
## Customization Logic
The `customise` command allows users to replace or rewrite specific parts of the documentation UI. It dynamically fetches registry information and computes target options based on the available layouts in the registry.
The `getSlotCode` helper function contains the logic to generate code blocks for slot replacement. It acts as a router based on the `name` of the slot being customized, returning the correct import and component structure for the user's `layout.tsx`.
Sources: [packages/cli/src/commands/customise.ts:22-179](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/commands/customise.ts#L22-L179), [packages/cli/src/commands/customise.ts:214-381](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/commands/customise.ts#L214-L381)
## File Tree Generation
The `tree` command converts a file system structure into a representable JSON format, which can be output as MDX or JavaScript components.
> [!NOTE]
> When generating the file tree, if a directory contains exactly one sub-item that is a name-indexed node, the command collapses the hierarchy level to reduce nesting in the final MDX output.
Sources: [packages/cli/src/commands/file-tree.ts:27-35](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/commands/file-tree.ts#L27-L35)
```typescript
// Example call to convert a file tree node to MDX
if (item.contents.length === 1 && 'name' in item.contents[0]) {
const child = item.contents[0];
return toNode({
...child,
name: `${item.name}/${child.name}`,
});
}
```
Sources: [packages/cli/src/commands/file-tree.ts:28-34](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/commands/file-tree.ts#L28-L34)
## Related
- [[App Templates]]
---
## App Templates
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/app-templates
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/create-app/src/plugins/orama-cloud.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/plugins/orama-cloud.ts)
- [packages/create-app/src/bin.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/bin.ts)
- [packages/base-ui/src/components/dialog/search.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/components/dialog/search.tsx)
- [packages/create-app/src/transform/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/transform/index.ts)
- [packages/create-app/src/plugins/ai.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/plugins/ai.ts)
- [packages/create-app/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/index.ts)
- [packages/create-app/src/plugins/next-use-takumi.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/plugins/next-use-takumi.ts)
- [packages/create-app/src/plugins/biome.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/plugins/biome.ts)
- [packages/preview/src/components/ai/search.tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/components/ai/search.tsx)
- [packages/radix-ui/src/components/dialog/search.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/dialog/search.tsx)
- [packages/create-app/src/constants.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/constants.ts)
- [packages/cli/src/commands/customise.ts](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/commands/customise.ts)
- [packages/create-app/src/transform/tanstack-start.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/transform/tanstack-start.ts)
- [packages/create-app/src/plugins/biome.base.json](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/plugins/biome.base.json)
- [packages/create-app/scripts/sync.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/scripts/sync.ts)
- [packages/create-app/src/plugins/next-use-src.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/plugins/next-use-src.ts)
- [packages/create-app/src/plugins/biome.next.json](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/plugins/biome.next.json)
- [packages/create-app/template/+orama-cloud/@app/components/search.tsx](https://github.com/blade47/fumadocs/blob/main/packages/create-app/template/%2Borama-cloud/%40app/components/search.tsx)
- [packages/create-app/src/plugins/oxlint.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/plugins/oxlint.ts)
- [packages/create-app/src/plugins/eslint.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/plugins/eslint.ts)
- [packages/base-ui/src/contexts/search.tsx](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/contexts/search.tsx)
- [packages/create-app/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/tsdown.config.ts)
- [packages/preview/src/layouts/config.tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/layouts/config.tsx)
- [packages/create-app-versions/package.json](https://github.com/blade47/fumadocs/blob/main/packages/create-app-versions/package.json)
- [packages/create-app/src/auto-install.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/auto-install.ts)
- [packages/create-app/tsconfig.json](https://github.com/blade47/fumadocs/blob/main/packages/create-app/tsconfig.json)
- [packages/create-app/.oxlintrc.json](https://github.com/blade47/fumadocs/blob/main/packages/create-app/.oxlintrc.json)
- [packages/create-app/vitest.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/vitest.config.ts)
- [packages/cli/src/commands/shared.ts](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/commands/shared.ts)
- [packages/base-ui/registry/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/registry/index.ts)
App Templates serve as the scaffolding engine for the Fumadocs ecosystem, enabling the rapid generation of consistent, production-ready documentation sites. By defining structured, framework-specific blueprints (e.g., Next.js, Waku, React Router, TanStack Start), these templates encapsulate complex configuration logic, dependency management, and routing setups into a unified, interactive CLI experience.
The primary problem solved by App Templates is the "first-mile" friction in modern web development. When starting a documentation project, developers face numerous choices regarding build systems, linter configurations, search providers, and AI integration. App Templates automate these decisions by allowing developers to select their preferred configuration during initialization, ensuring that the generated project adheres to project-wide best practices while significantly reducing manual setup time.
Architecturally, the template system acts as a plugin-based orchestration layer. When an app is generated, the system creates a context representing the destination directory and chosen template, then sequentially invokes registered plugins. Each plugin can manipulate the filesystem, modify `package.json` dependencies, or transform existing source code (e.g., injecting search dialog providers into a root layout) via an Abstract Syntax Tree (AST) transformer. This design facilitates extensibility; adding a new feature like AI chat or a specific linter becomes a matter of registering a plugin that hooks into the template lifecycle.
## Project Lifecycle and Generation Flow
The template generation flow is managed by the `create()` function in `packages/create-app/src/index.ts`. The process follows a strict sequential pipeline that ensures modular, predictable output.
```mermaid
flowchart TD
A["User initiates
CLI Command"] --> B["Group Prompts
(name, template, plugins)"]
B --> C["Plugin Orchestration"]
C --> D["File Copying
(base template structure)"]
D --> E["Package.json Initialization"]
E --> F["Plugin afterWrite()
AST Transformations"]
F --> G["Dependency Install &
Git Initialization"]
```
Sources: [packages/create-app/src/bin.ts:56-282](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/bin.ts#L56-L282), [packages/create-app/src/index.ts:64-126](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/index.ts#L64-L126)
## Template Plugin System
Plugins are the primary extension mechanism for template modification. A `TemplatePlugin` defines lifecycle hooks that allow modifications at specific stages of the project generation lifecycle.
| Hook | Responsibility |
| :--- | :--- |
| `template()` | Dynamically modifies template info (e.g., changing `appDir` to `src`). |
| `packageJson()` | Merges dependencies and scripts into `package.json` before writing. |
| `readme()` | Appends documentation or instructions to `README.md`. |
| `afterWrite()` | Executes side effects, filesystem operations, or AST modifications. |
Sources: [packages/create-app/src/index.ts:51-62](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/index.ts#L51-L62)
> [!TIP]
> The `afterWrite` hook is the most powerful plugin lifecycle point. Because files are already persisted to the filesystem, plugins can reliably use AST transformers to inject imports or components into existing files.
## AST Transformation Mechanism
To maintain project integrity during configuration, the system utilizes AST transformation (via `ts-morph`) rather than simple string injection. This ensures that generated code is syntactically correct and respects existing project structures.
The core transformer functionality is encapsulated in `packages/create-app/src/transform/index.ts`. For instance, when adding a search provider, the `rootProvider` function scans for a `RootProvider` component and safely injects the search prop without overwriting existing configuration.
```typescript
// Example: Adding search dialog using AST injection
await rootProvider(this, (mod) => mod.addSearchDialog('@/components/search'));
```
Sources: [packages/create-app/src/transform/index.ts:17-55](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/transform/index.ts#L17-L55)
> [!WARNING]
> AST injections verify if a property (e.g., `search`) already exists before attempting to inject a new one. This guard prevents duplicate definitions, which would cause runtime errors or configuration conflicts.
## Search Solution Integration
App Templates provide specialized logic for search implementations, most notably for Orama Cloud. This requires both client-side components and server-side synchronization scripts to ensure search indexes remain current.
The `oramaCloud()` plugin handles this by:
1. Copying search components into the app directory.
2. Registering an API route for index exports.
3. Generating a `scripts/sync-content.ts` utility that fetches the rendered `static.json` and pushes it to Orama Cloud via an authenticated request.
Sources: [packages/create-app/src/plugins/orama-cloud.ts:8-107](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/plugins/orama-cloud.ts#L8-L107)
## Configuration and Version Control
Dependency versions are centralized to ensure consistency across template variations. The `packages/create-app/src/constants.ts` file consolidates versioning from workspace packages, preventing version drift when a new dependency is introduced.
| Resource | Scope |
| :--- | :--- |
| `sourceDir` | Source templates directory for scaffolding files. |
| `templates` | Array of `TemplateInfo` objects defining supported scaffolds. |
| `depVersions` | Computed dependency mapping used during `package.json` creation. |
Sources: [packages/create-app/src/constants.ts:8-88](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/constants.ts#L8-L88)
> [!NOTE]
> Workspace dependencies defined in `package.json` as `workspace:*` are automatically resolved to specific versions from `depVersions` during the initialization of `package.json` inside the `create` flow.
## AI Chat Integration
AI integration is implemented as an optional plugin that installs component dependencies via the CLI registry and modifies the documentation layout to include an `AISearch` component.
The `addAIChat` flow follows:
1. `installer.install('ai/provider')`: Installs specific UI components for the chosen AI provider.
2. `createSourceFile(filePath)`: Parses the existing layout file.
3. `element.setBodyText(...)`: Prepends the `AISearch` component to the `DocsLayout` content children.
4. `file.addImportDeclarations(...)`: Injects necessary components and hook imports.
Sources: [packages/create-app/src/plugins/ai.ts:39-109](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/plugins/ai.ts#L39-L109)
## Related
- [[Command Line Interface]]
---
## EPUB Exporter
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/epub-exporter
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/epub/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/epub/src/index.ts)
- [packages/cli/src/commands/export-epub.ts](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/commands/export-epub.ts)
- [packages/core/src/mdx-plugins/remark-structure.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-structure.ts)
- [packages/openapi/src/utils/pages/preset-auto.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/pages/preset-auto.ts)
- [packages/openapi/src/utils/pages/builder.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/pages/builder.ts)
- [packages/asyncapi/src/generate-file.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/generate-file.ts)
- [packages/core/src/mdx-plugins/rehype-toc.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/rehype-toc.ts)
- [packages/mdx/src/loaders/mdx/remark-postprocess.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/mdx/remark-postprocess.ts)
- [packages/core/src/mdx-plugins/remark-llms.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-llms.ts)
- [packages/core/src/source/llms.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/llms.ts)
- [packages/asyncapi/src/utils/pages/preset-auto.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/utils/pages/preset-auto.ts)
- [packages/base-ui/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/tsdown.config.ts)
- [packages/python/src/convert.ts](https://github.com/blade47/fumadocs/blob/main/packages/python/src/convert.ts)
- [packages/radix-ui/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/tsdown.config.ts)
- [packages/asyncapi/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/tsdown.config.ts)
- [packages/epub/src/markdown-to-html.ts](https://github.com/blade47/fumadocs/blob/main/packages/epub/src/markdown-to-html.ts)
- [packages/obsidian/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/index.ts)
- [packages/python/src/write.ts](https://github.com/blade47/fumadocs/blob/main/packages/python/src/write.ts)
- [packages/asyncapi/src/utils/pages/to-text.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/utils/pages/to-text.ts)
- [packages/epub/src/default-styles.ts](https://github.com/blade47/fumadocs/blob/main/packages/epub/src/default-styles.ts)
- [packages/openapi/src/utils/pages/to-text.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/pages/to-text.ts)
- [packages/epub/src/types.ts](https://github.com/blade47/fumadocs/blob/main/packages/epub/src/types.ts)
- [packages/epub/package.json](https://github.com/blade47/fumadocs/blob/main/packages/epub/package.json)
- [packages/openapi/src/utils/pages/to-static-data.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/pages/to-static-data.ts)
- [packages/epub/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/epub/tsdown.config.ts)
- [packages/content-collections/src/configuration.ts](https://github.com/blade47/fumadocs/blob/main/packages/content-collections/src/configuration.ts)
- [packages/obsidian/src/convert.ts](https://github.com/blade47/fumadocs/blob/main/packages/obsidian/src/convert.ts)
- [packages/preview/src/lib/source/storage.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/lib/source/storage.ts)
- [packages/epub/src/toc-builder.ts](https://github.com/blade47/fumadocs/blob/main/packages/epub/src/toc-builder.ts)
- [packages/content/src/runtime.ts](https://github.com/blade47/fumadocs/blob/main/packages/content/src/runtime.ts)
The EPUB Exporter is a dedicated subsystem for converting Fumadocs documentation into the standard EPUB format. By transforming Markdown/MDX content into semantic HTML and packaging it according to the IDPF EPUB 3 specification, it allows users to consume documentation offline or on dedicated e-readers, bridging the gap between web-based documentation and portable digital book formats.
The exporter acts as an adapter, taking the `source` object provided by Fumadocs loaders and navigating the site structure to ensure the EPUB's Table of Contents (TOC) mirrors the logical navigation tree of the documentation. This ensures a coherent reading experience, preserving hierarchy and ordering defined in the site metadata.
At its architectural core, the exporter relies on `epub-gen-memory` to construct the EPUB bundle in memory, avoiding unnecessary filesystem I/O during the compilation phase. It employs a post-processing pipeline that transforms Markdown to HTML, resolving relative image paths and applying default styles, making it highly configurable for developers who need to customize the aesthetic or inclusion criteria of their exported books.
## Public Interface: `exportEpub`
The `exportEpub` function is the primary entry point, orchestrating the document collection and conversion process. It accepts an `EpubExportOptions` object, which requires a Fumadocs `source` and various metadata (title, author, publisher).
```typescript
import { exportEpub } from 'fumadocs-epub';
import { source } from '@/lib/source';
const buffer = await exportEpub({
source,
title: 'My Documentation',
author: 'My Team',
cover: '/cover.png',
});
```
Sources: [packages/epub/src/index.ts:64-150](https://github.com/blade47/fumadocs/blob/main/packages/epub/src/index.ts#L64-L150)
## Page Navigation and Ordering
To generate a sensible EPUB, the exporter must respect the documentation’s navigation structure. The system utilizes `getPagesInTreeOrder`, which recursively flattens the page tree to establish the order of chapters. If the tree contains no navigation items (common in multi-language setups), the exporter falls back to the flat list provided by `source.getPages()`.
> [!NOTE]
> The order of pages in the EPUB is dictated by the site's navigation tree structure. If a page is not part of the tree, it is relegated to the fallback logic, which may not maintain the intended sequence.
Sources: [packages/epub/src/index.ts:92-99](https://github.com/blade47/fumadocs/blob/main/packages/epub/src/index.ts#L92-L99), [packages/epub/src/toc-builder.ts:5-25](https://github.com/blade47/fumadocs/blob/main/packages/epub/src/toc-builder.ts#L5-L25)
## Markdown to HTML Transformation
The transformation pipeline is handled by `markdownToHtml`, which uses a `unified` processor to sanitize and convert raw MDX content. A custom remark plugin `remarkResolveImg` is injected into the pipeline. This plugin visits all `image` nodes in the Markdown AST, invoking `resolveImageSrc` to convert local or relative path references into absolute URLs or `file://` URIs, ensuring that images within the EPUB display correctly regardless of the reader's environment.
Sources: [packages/epub/src/markdown-to-html.ts:10-37](https://github.com/blade47/fumadocs/blob/main/packages/epub/src/markdown-to-html.ts#L10-L37)
## CLI and Scaffolding
The CLI command `export-epub` manages the lifecycle of the generation process, especially for frameworks like Next.js that do not generate static files. It includes a scaffolding feature that creates a dedicated API route (`/export/epub/route.ts`) within the user's project, protected by an `EXPORT_SECRET`.
| Feature | Next.js Implementation | Other Frameworks |
| :--- | :--- | :--- |
| **Generation** | Fetches via API route | Copies from predefined path |
| **Route** | Scaffolded at `app/export/epub/route.ts` | Not required |
| **Auth** | Required `EXPORT_SECRET` | Depends on CI environment |
Sources: [packages/cli/src/commands/export-epub.ts:27-169](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/commands/export-epub.ts#L27-L169)
## Image Path Resolution
The exporter implements robust logic to resolve cover images and embedded images. `resolveCoverPath` distinguishes between remote URLs, absolute paths in the `public` directory, and relative paths based on the current working directory (`cwd`). By mapping paths to file URLs, the exporter guarantees that `epub-gen-memory` can successfully pull the resources into the archive.
> [!CAUTION]
> If a file is not found, the image resolution logic will splice the image node out of the document entirely during the remark processing phase, preventing broken image placeholders in the final EPUB.
Sources: [packages/epub/src/index.ts:26-39](https://github.com/blade47/fumadocs/blob/main/packages/epub/src/index.ts#L26-L39), [packages/epub/src/markdown-to-html.ts:32-34](https://github.com/blade47/fumadocs/blob/main/packages/epub/src/markdown-to-html.ts#L32-L34)
## EPUB Configuration Logic
The `epubOptions` struct maps internal options to the generator's format. A critical design decision is the use of `prependChapterTitles: true` and `numberChaptersInTOC: true`. These are enabled by default because Fumadocs content often relies on existing heading hierarchy, and forcing these titles into the EPUB's explicit TOC facilitates easier navigation in standard e-readers that might not interpret nested HTML structure as deeply as a web browser.
Sources: [packages/epub/src/index.ts:124-135](https://github.com/blade47/fumadocs/blob/main/packages/epub/src/index.ts#L124-L135)
## Related
- [[Command Line Interface]]
---
## Local Markdown Dev
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/local-markdown-dev
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/local-md/src/eval-estree-expression/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/eval-estree-expression/index.ts)
- [packages/local-md/src/dev/node-server.ts](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/dev/node-server.ts)
- [packages/local-md/src/dev/react-client.ts](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/dev/react-client.ts)
- [packages/local-md/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/index.ts)
- [packages/core/src/content/md.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/content/md.ts)
- [packages/local-md/src/md/renderer.ts](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/md/renderer.ts)
- [packages/local-md/src/dev/node-client.ts](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/dev/node-client.ts)
- [packages/local-md/package.json](https://github.com/blade47/fumadocs/blob/main/packages/local-md/package.json)
- [packages/preview/src/cli/commands.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/cli/commands.ts)
- [packages/core/src/mdx-plugins/remark-llms.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-llms.ts)
- [packages/preview/src/components/hot-reload.tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/components/hot-reload.tsx)
- [packages/mdx/src/loaders/mdx/remark-include.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/loaders/mdx/remark-include.ts)
- [packages/mdx/src/runtime/server.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/server.ts)
- [packages/core/src/source/llms.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/source/llms.ts)
- [packages/local-md/src/js/executor-virtual.ts](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/js/executor-virtual.ts)
- [packages/preview/src/lib/source/watcher.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/lib/source/watcher.ts)
- [packages/preview/src/waku.server.tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/waku.server.tsx)
- [packages/local-md/src/bin.ts](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/bin.ts)
- [packages/asyncapi/src/server/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/server/index.tsx)
- [packages/asyncapi/src/ui/components/markdown.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/components/markdown.tsx)
- [packages/local-md/src/dev/shared.ts](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/dev/shared.ts)
- [packages/local-md/src/md/compiler.ts](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/md/compiler.ts)
- [packages/preview/src/components/markdown.tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/components/markdown.tsx)
- [packages/local-md/src/client.ts](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/client.ts)
- [packages/openapi/src/ui/components/markdown.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/components/markdown.tsx)
- [packages/local-md/src/storage.ts](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/storage.ts)
- [packages/core/src/mdx-plugins/remark-llms.runtime.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-llms.runtime.ts)
- [packages/preview/src/lib/md.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/lib/md.ts)
- [packages/local-md/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/local-md/tsdown.config.ts)
- [packages/local-md/src/js/executor.ts](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/js/executor.ts)
"Local Markdown Dev" provides the infrastructure for Fumadocs to handle local Markdown and MDX content, including storage management, compilation pipelines, and a development-time hot-reloading mechanism. By bridging raw filesystem content with the documentation framework, it allows for efficient content scanning, caching, and dynamic revalidation during development.
The subsystem is architected around a central storage engine that manages file parsing and invalidation. It exposes interfaces for both static and dynamic sources, enabling developers to integrate filesystem-based documentation into their sites seamlessly. It maintains a robust separation of concerns between raw content parsing, Markdown compilation (using `remark` and `rehype`), and runtime execution of code embedded within documentation files.
In addition to core compilation, this component introduces a WebSocket-based development server. This server facilitates communication between the filesystem and the client, ensuring that any modifications to source files automatically trigger UI refreshes via `router.refresh()`. This architecture addresses the latency and manual re-run overhead typical in documentation development, offering a high-performance experience that remains compatible with modern JavaScript environments.
## Storage and File Management
The storage layer handles the discovery and parsing of local documentation files. It interacts with the filesystem via glob patterns to create a representation of pages and metadata, providing an invalidation mechanism that is crucial for the dev-server functionality.
```typescript
// Accessing file storage and invalidation
const storage = createStorage(config);
// ... later, triggered by watcher events:
storage.invalidateCache(absolutePath);
```
Sources: [packages/local-md/src/storage.ts:25-140](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/storage.ts#L25-L140), [packages/local-md/src/index.ts:171](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/index.ts#L171)
## Markdown Compilation Pipeline
The compilation system is modular, supporting different configurations for standard Markdown and MDX. It utilizes `remark` and `rehype` plugin architectures to process documents into AST or JS outputs.
> [!IMPORTANT]
> The compiler handles MDX files by generating JS functions, while standard Markdown files are processed into HAST trees for efficient execution.
Sources: [packages/local-md/src/md/compiler.ts:62-162](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/md/compiler.ts#L62-L162)
## Virtual JavaScript Execution
For Markdown files that require executing code (e.g., dynamic components), the subsystem includes a virtual JS engine (`executorVirtual`). This is an implementation of `estree-util-build-jsx` logic that evaluates AST nodes within a controlled context, bypassing the need for a full Node.js `vm` module, which is beneficial for edge-runtime compatibility.
```mermaid
flowchart TD
A["visit(node, context)"] --> B{node.type}
B --> C["Call handler
(e.g. Identifier, ObjectExpression)"]
C --> D["Bind patterns/scope"]
D --> E["Return evaluated value"]
```
Sources: [packages/local-md/src/eval-estree-expression/index.ts:82-837](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/eval-estree-expression/index.ts#L82-L837), [packages/local-md/src/js/executor-virtual.ts:7-21](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/js/executor-virtual.ts#L7-L21)
## Dev Server and Hot Reloading
The dev server uses `chokidar` to observe filesystem changes. When a file is modified, it broadcasts a WebSocket event to the connected clients. The client-side `DevClient` receives these events and calls `router.refresh()` to update the page view in real-time.
```mermaid
sequenceDiagram
participant FS as Filesystem
participant DS as Dev Server
participant DC as Dev Client
FS->>DS: File Changed
DS->>DC: WebSocket: { type: 'change', absolutePath: '...' }
DC->>DC: router.refresh()
```
Sources: [packages/local-md/src/dev/node-server.ts:33-163](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/dev/node-server.ts#L33-L163), [packages/local-md/src/dev/react-client.ts:6-40](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/dev/react-client.ts#L6-L40)
## Design Trade-offs
| Design Choice | Benefit | Cost |
| :--- | :--- | :--- |
| `ExpressionSync` engine | Portable, works in Workerd/Edge | Limited JS feature support |
| WebSocket-based reloads | Extremely low latency | Requires open port/server-side state |
| `WeakMap` for caching | Automatic garbage collection of AST results | Potentially higher memory footprint per page |
Sources: [packages/local-md/src/eval-estree-expression/index.ts:2-4](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/eval-estree-expression/index.ts#L2-L4), [packages/local-md/src/index.ts:92](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/index.ts#L92), [packages/local-md/src/dev/node-server.ts:58-62](https://github.com/blade47/fumadocs/blob/main/packages/local-md/src/dev/node-server.ts#L58-L62)
## Related
- [[Preview Environment]]
---
## Preview Environment
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/preview-environment
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/preview/src/components/ai/search.tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/components/ai/search.tsx)
- [packages/preview/src/pages/[...slugs].tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/pages/%5B...slugs%5D.tsx)
- [packages/preview/src/pages/_api/api/chat.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/pages/_api/api/chat.ts)
- [packages/asyncapi/src/ui/contexts/api.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/contexts/api.tsx)
- [packages/openapi/src/ui/operation/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/index.tsx)
- [packages/mdx/src/runtime/dynamic.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/dynamic.ts)
- [packages/openapi/src/ui/contexts/api.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/contexts/api.tsx)
- [packages/create-app/src/plugins/ai.ts](https://github.com/blade47/fumadocs/blob/main/packages/create-app/src/plugins/ai.ts)
- [packages/preview/src/lib/source/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/lib/source/index.ts)
- [packages/preview/src/pages/_api/img.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/pages/_api/img.ts)
- [packages/api-docs/src/components/schema/client.tsx](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/components/schema/client.tsx)
- [packages/preview/src/config/load-runtime.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/config/load-runtime.ts)
- [packages/preview/src/cli/commands.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/cli/commands.ts)
- [packages/preview/src/components/hot-reload.tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/components/hot-reload.tsx)
- [packages/mdx/src/runtime/browser.tsx](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/runtime/browser.tsx)
- [packages/preview/src/layouts/config.tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/layouts/config.tsx)
- [packages/asyncapi/src/ui/api-page.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/api-page.tsx)
- [packages/preview/src/lib/ai.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/lib/ai.ts)
- [packages/preview/src/pages/_api/api/search.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/pages/_api/api/search.ts)
- [packages/core/src/framework/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/core/src/framework/index.tsx)
- [packages/preview/src/pages/_root.tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/pages/_root.tsx)
- [packages/preview/src/lib/env.d.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/lib/env.d.ts)
- [packages/core/src/mdx-plugins/remark-llms.runtime.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-llms.runtime.ts)
- [packages/preview/src/components/markdown.tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/components/markdown.tsx)
- [packages/core/src/framework/waku.tsx](https://github.com/blade47/fumadocs/blob/main/packages/core/src/framework/waku.tsx)
- [packages/preview/src/waku.server.tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/waku.server.tsx)
- [packages/openapi/src/ui/operation/context.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/context.tsx)
- [packages/core/src/framework/react-router.tsx](https://github.com/blade47/fumadocs/blob/main/packages/core/src/framework/react-router.tsx)
- [packages/preview/src/pages/_api/og/[...slugs]/image.webp.tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/pages/_api/og/%5B...slugs%5D/image.webp.tsx)
- [packages/preview/src/components/provider.tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/components/provider.tsx)
The Preview Environment subsystem provides a robust, real-time development and documentation rendering experience. By integrating dynamic compilation, filesystem watching, and reactive UI components, it enables developers to view documentation content as it is authored while supporting advanced features like AI-powered search, API playground interactions, and hot reloading.
The environment acts as a bridge between static content and a live, interactive UI. It orchestrates the loading of documentation sources from the filesystem, processes MDX content through a unified compiler, and renders the result within a framework-agnostic layout. Central to this architecture is the decoupling of the content engine from the rendering framework, allowing for consistent behavior whether running in development or a production-preview context.
By centralizing configuration (via `load-runtime.ts`) and leveraging hot-reloading mechanisms (via `waku.server.tsx`), the Preview Environment ensures that changes to source files are immediately reflected in the user's view. This system eliminates the latency typical of static site regeneration during development, creating a fluid, high-fidelity experience for authors and technical documentation consumers alike.
## Dynamic Content Compilation
The system employs a dynamic compilation pipeline to transform Markdown and MDX content on the fly. This avoids pre-build overhead and ensures the latest document content is always available. When a request is made, the `compiler` (a `createMarkdownCompiler` instance) processes the source using remark and rehype plugins, including support for GFM, math, code blocks, and Mermaids diagrams.
The pipeline specifically handles file resolution through `getPage` and compiles the document content within the `MdContent` component. If compilation fails, the system catches the error and renders an error page displaying the stack trace, preventing the entire application from crashing.
```mermaid
flowchart TD
A["Request Source Page"] --> B{"Page Exists?"}
B -- Yes --> C["Compile
Markdown"]
C -- Success --> D["Render Page"]
C -- Error --> E["Render Error Page"]
B -- No --> F["Render 404"]
```
Sources: [packages/preview/src/pages/[...slugs].tsx:29-46,140-194](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/pages/[...slugs].tsx#L29-L46,L140-L194)
## AI Search Integration
The Preview Environment includes an integrated AI chat interface, `AISearch`, which allows users to query documentation interactively. This component uses the `@ai-sdk/react` library to stream responses from an AI model.
The chat mechanism relies on a tool-calling pattern where the AI can invoke a `search` tool. This tool queries a local Flexsearch index built at runtime from the documentation source files. The system guards against excessive usage via a simple IP-based rate limiter located in the API endpoint.
> [!IMPORTANT]
> The rate limiter identifies clients by their IP address derived from headers (`x-forwarded-for` or `cf-connecting-ip`). If a bucket reaches `rateLimitMaxRequests` (20), the system returns a `429` status code and a `Retry-After` header.
```mermaid
sequenceDiagram
participant User
participant Frontend
participant ChatAPI
participant SearchTool
User->>Frontend: Send Query
Frontend->>ChatAPI: POST /api/chat
ChatAPI->>SearchTool: Invoke search tool
SearchTool->>ChatAPI: Returns search results
ChatAPI-->>Frontend: Stream AI response
```
Sources: [packages/preview/src/components/ai/search.tsx:292-304,331-388](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/components/ai/search.tsx#L292-L304,L331-L388), [packages/preview/src/pages/_api/api/chat.ts:55-156](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/pages/_api/api/chat.ts#L55-L156)
## Development Hot Reloading
To facilitate immediate feedback, the system monitors the filesystem for changes. The `initHotReload` function in the server entry point establishes a WebSocket connection with the client-side `HotReload` component.
When the file watcher detects a change, it performs two critical steps:
1. It deletes the specific file from the `filesCache`.
2. It calls `getSource.revalidate(false)` to force a refresh of the content source, ensuring that subsequent requests pull the modified data.
The client-side component then receives a `revalidate` event and triggers a `router.reload()`. This creates a seamless development loop for content authors.
Sources: [packages/preview/src/waku.server.tsx:56-92](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/waku.server.tsx#L56-L92), [packages/preview/src/components/hot-reload.tsx:6-26](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/components/hot-reload.tsx#L6-L26)
## API Documentation and Schemas
For API-driven documentation, the system leverages `ServerProvider` and `Operation` contexts. These components maintain the state of API servers and request schemas. The schema UI uses a path-tracking system (`PathItemType[]`) to allow users to navigate nested objects within OpenAPI definitions via a popover.
| Component | Responsibility |
| :--- | :--- |
| `ServerProvider` | Manages active API server URL and variables. |
| `OperationProvider` | Handles state for example requests and update listeners. |
| `SchemaUI` | Manages nested object navigation and search. |
Sources: [packages/asyncapi/src/ui/contexts/api.tsx:47-124](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/contexts/api.tsx#L47-L124), [packages/openapi/src/ui/operation/context.tsx:20-82](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/context.tsx#L20-L82), [packages/api-docs/src/components/schema/client.tsx:83-185](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/components/schema/client.tsx#L83-L185)
## Configuration and Runtime Initialization
The application configuration is parsed dynamically. The system provides a centralized `getConfigRuntime` function which resolves the config path, loads it, and returns a validated `ParsedAppConfig`. This is then injected throughout the application via the source loader, ensuring that project-specific directories (where Markdown files are found) are correctly identified during startup.
```typescript
// Initializing the configuration for the preview app
const configPath = await findConfigPath();
const config = await loadConfig(configPath);
```
Sources: [packages/preview/src/config/load-runtime.ts:26-31](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/config/load-runtime.ts#L26-L31)
## Asset Handling
The system exposes a custom image proxy API (`/api/img`) to resolve image assets from projects that may exist outside the public directory. It checks for assets in the page's relative directory, configured asset directories, or the project root. This ensures documentation images remain linked correctly even in complex file structures.
> [!NOTE]
> The file lookup logic follows a strict order: relative page directory → project-configured assets directories → root public directory. If a file is not found, the proxy falls back to a `404`.
Sources: [packages/preview/src/pages/_api/img.ts:19-66](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/pages/_api/img.ts#L19-L66)
## Related
- [[Local Markdown Dev]]
---
## Story Visualizer
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/story-visualizer
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/preview/src/pages/[...slugs].tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/pages/%5B...slugs%5D.tsx)
- [packages/story/src/client/with-control.tsx](https://github.com/blade47/fumadocs/blob/main/packages/story/src/client/with-control.tsx)
- [packages/story/src/webpack/story.ts](https://github.com/blade47/fumadocs/blob/main/packages/story/src/webpack/story.ts)
- [packages/story/src/type-tree/builder.ts](https://github.com/blade47/fumadocs/blob/main/packages/story/src/type-tree/builder.ts)
- [packages/story/src/index.tsx](https://github.com/blade47/fumadocs/blob/main/packages/story/src/index.tsx)
- [packages/api-docs/src/components/schema/client.tsx](https://github.com/blade47/fumadocs/blob/main/packages/api-docs/src/components/schema/client.tsx)
- [packages/mdx/src/webpack/meta.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/webpack/meta.ts)
- [packages/story/src/vite/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/story/src/vite/index.ts)
- [packages/preview/src/lib/source/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/lib/source/index.ts)
- [packages/cli/src/commands/customise.ts](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/commands/customise.ts)
- [packages/preview/src/config/load-runtime.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/config/load-runtime.ts)
- [packages/radix-ui/package.json](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/package.json)
- [packages/asyncapi/src/ui/components/markdown.tsx](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/src/ui/components/markdown.tsx)
- [packages/story/src/client/compiled.tsx](https://github.com/blade47/fumadocs/blob/main/packages/story/src/client/compiled.tsx)
- [packages/radix-ui/src/components/dynamic-codeblock.core.tsx](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/components/dynamic-codeblock.core.tsx)
- [packages/preview/src/components/hot-reload.tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/components/hot-reload.tsx)
- [packages/openapi/src/ui/components/markdown.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/components/markdown.tsx)
- [packages/preview/src/components/markdown.tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/components/markdown.tsx)
- [packages/preview/src/pages/_root.tsx](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/pages/_root.tsx)
- [packages/story/src/utils/transform.ts](https://github.com/blade47/fumadocs/blob/main/packages/story/src/utils/transform.ts)
- [packages/core/src/mdx-plugins/remark-llms.runtime.ts](https://github.com/blade47/fumadocs/blob/main/packages/core/src/mdx-plugins/remark-llms.runtime.ts)
- [packages/openapi/src/ui/operation/context.tsx](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/ui/operation/context.tsx)
- [packages/story/src/utils/generate.ts](https://github.com/blade47/fumadocs/blob/main/packages/story/src/utils/generate.ts)
- [packages/preview/src/config/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/config/index.ts)
- [packages/mdx/src/webpack/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/mdx/src/webpack/index.ts)
- [packages/story/src/vite/client.tsx](https://github.com/blade47/fumadocs/blob/main/packages/story/src/vite/client.tsx)
- [packages/preview/fumadocs.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/fumadocs.config.ts)
- [packages/story/css/preset.css](https://github.com/blade47/fumadocs/blob/main/packages/story/css/preset.css)
- [packages/cli/src/commands/shared.ts](https://github.com/blade47/fumadocs/blob/main/packages/cli/src/commands/shared.ts)
- [packages/preview/src/cli/loader.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/cli/loader.ts)
The Story Visualizer is a sophisticated documentation subsystem designed to bridge the gap between static component documentation and interactive development environments. Its primary purpose is to automatically extract type information from TypeScript components at build time (or runtime, via caching) and generate an interactive control interface for those components within documentation pages.
By leveraging `ts-morph` to analyze static types, the Story Visualizer eliminates the manual overhead of writing and maintaining complex component props tables or playground controls. It generates a "Type Tree"—a structural representation of component props—which is then consumed by the client-side `WithControl` component to render an interactive editor. This system is architected for maximum automation, ensuring that changes to component interfaces are reflected in the documentation immediately upon build.
The architecture centers around a high-precision type-to-node transformation pipeline. It handles complex TypeScript features such as unions, intersections, literal types, and object properties by recursively walking the type graph and converting it into a JSON-serializable `TypeNode` format. This serialization enables the subsystem to work across various environments, from standard build-time environments (Vite/Webpack) to restricted serverless environments where full compiler access might be prohibited.
## The Type Transformation Pipeline
The core mechanism for generating story controls is the `TypeTreeBuilder`. This system uses a modular handler pattern to convert `ts-morph` `Type` objects into a custom `TypeNode` schema. The builder walks the type graph, applying specific handlers for primitive types, enums, arrays, and complex objects.
The traversal is governed by a prioritized sequence of handlers. If a handler cannot process a type, it delegates to the `next` handler.
```mermaid
flowchart TD
A["TypeTreeBuilder.typeToNode()"] --> B["callHandler()"]
B --> C["Check Handler Cache"]
C -- "Cache Hit" --> D["Return cached TypeNode"]
C -- "Cache Miss" --> E["Execute Handler"]
E --> F{"Handler Logic"}
F --> G["baseHandler"]
F --> H["Custom Handlers (e.g., literalEnumHandler)"]
G --> I["Recursively call root()"]
I --> B
```
Sources: [packages/story/src/type-tree/builder.ts:233-282](https://github.com/blade47/fumadocs/blob/main/packages/story/src/type-tree/builder.ts#L233-L282)
> [!NOTE]
> The `cache` is implemented using `Map>` to prevent memory leaks while ensuring deterministic performance when processing deep type trees. The `WeakMap` uses the `Type` object itself as a key, ensuring that nodes are garbage collected when the compiler project is disposed.
## Build-time Transformation
The `transformStoryFile` utility performs the "magic" of story generation at build time. It intercepts files matching the story pattern, scans for `defineStory` function calls, and generates the necessary metadata.
1. **Detection**: `findDefineStoryCalls` identifies all `defineStory` invocations.
2. **Analysis**: `generateControls` creates a temporary type alias in the source file, which acts as a bridge to obtain the component's props type.
3. **Serialization**: The generated `TypeNode` tree is serialized into a JSON string and injected back into the source file as an `_generated` property.
```mermaid
sequenceDiagram
participant P as Source File
participant T as transformStoryFile
participant C as Compiler (ts-morph)
participant G as generateControls
P->>T: File Content
T->>C: Create Virtual SourceFile
T->>G: Extract Type Information
G->>C: Get Type Alias
C-->>G: TypeNode[]
G-->>T: Serialized JSON
T->>P: Inject _generated property
```
Sources: [packages/story/src/utils/transform.ts:25-45](https://github.com/blade47/fumadocs/blob/main/packages/story/src/utils/transform.ts#L25-L45)
## Control Injection and Interaction
Once a story is loaded in the browser, the `WithControl` component provides the interactive UI. It uses the `stf` (Story State Framework) to manage the state of component properties.
The key design choice here is the use of `useDeferredValue` for argument updates. This ensures that the documentation interface remains responsive even if the component being visualized is computationally expensive or prone to re-rendering.
| Feature | Mechanism | Benefit |
| :--- | :--- | :--- |
| **State Management** | `StfProvider` context | Decouples state from UI hierarchy |
| **Type Resolution** | `Deserialize(JSON)` | Allows runtime type enforcement |
| **Error Handling** | `ErrorBoundary` | Prevents component failures from crashing the page |
| **Updates** | `useListener` | Batching updates via `setTimeout` to limit re-renders |
Sources: [packages/story/src/client/with-control.tsx:28-81](https://github.com/blade47/fumadocs/blob/main/packages/story/src/client/with-control.tsx#L28-L81)
## Data Structure: TypeNode
The `TypeNode` is the foundational structure for story controls. It is a discriminated union that allows the `WithControl` component to dynamically render the appropriate input field for any given property type.
| Node Type | Purpose | Key Attributes |
| :--- | :--- | :--- |
| `string` | Text input | None |
| `number` | Numeric input | None |
| `boolean` | Checkbox/Switch | None |
| `literal` | Fixed choice | `value` |
| `enum` | Select dropdown | `members` (label, value) |
| `object` | FieldSet grouping | `properties` (name, type, required) |
| `union` | Polymorphic controls | `types` (array of TypeNode) |
Sources: [packages/story/src/type-tree/types.ts](https://github.com/blade47/fumadocs/blob/main/packages/story/src/type-tree/types.ts)
## Error Handling and Invariants
The Story Visualizer employs strict guards during the traversal of type definitions to avoid infinite recursions or unhandled edge cases.
> [!WARNING]
> `TypeToNodeFlag.NoIntersection` is a critical flag used to prevent the builder from descending infinitely into recursive intersections. When set, it forces the parser to treat the intersection as a terminal node.
> [!IMPORTANT]
> The `collapse` function performs a deterministic state reduction. It should be used when `fixed` values are provided by the user. If an object property is provided as a fixed value, it recursively prunes the `TypeNode` tree to remove irrelevant properties, optimizing the rendering process.
Sources: [packages/story/src/type-tree/builder.ts:5-8, 284-317](https://github.com/blade47/fumadocs/blob/main/packages/story/src/type-tree/builder.ts#L5-L8)
## Worked Example: Defining a Story
Developers can define a story with custom controls or use the default generated ones.
```typescript
import { defineStoryFactory } from '@fumadocs/story';
import { Button } from './components/button';
const { defineStory } = defineStoryFactory();
export const MyStory = defineStory('/path/to/component.tsx', {
Component: Button,
args: {
initial: { variant: 'primary', children: 'Click me' },
fixed: { disabled: true },
controls: {
transform: (node) => {
// Custom logic to modify generated controls
return node;
}
}
}
});
```
Sources: [packages/story/src/index.tsx:105-157](https://github.com/blade47/fumadocs/blob/main/packages/story/src/index.tsx#L105-L157)
## Related
- [[Preview Environment]]
---
## Shadcn Integration
URL: https://www.doc0.app/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/shadcn-integration
Relevant source files
The following files were used as context for generating this wiki page:
- [packages/shadcn/src/manual-installation.ts](https://github.com/blade47/fumadocs/blob/main/packages/shadcn/src/manual-installation.ts)
- [packages/tailwind/src/typography/styles.ts](https://github.com/blade47/fumadocs/blob/main/packages/tailwind/src/typography/styles.ts)
- [packages/base-ui/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/tsdown.config.ts)
- [packages/openapi/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/tsdown.config.ts)
- [packages/radix-ui/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/tsdown.config.ts)
- [packages/asyncapi/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/asyncapi/tsdown.config.ts)
- [packages/tailwind/src/typography/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/tailwind/src/typography/index.ts)
- [packages/shadcn/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/shadcn/tsdown.config.ts)
- [packages/shadcn/src/rsc.tsx](https://github.com/blade47/fumadocs/blob/main/packages/shadcn/src/rsc.tsx)
- [packages/radix-ui/css/shadcn.css](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/css/shadcn.css)
- [packages/radix-ui/css/preset.css](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/css/preset.css)
- [packages/base-ui/css/shadcn.css](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/css/shadcn.css)
- [packages/base-ui/css/preset.css](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/css/preset.css)
- [packages/shadcn/package.json](https://github.com/blade47/fumadocs/blob/main/packages/shadcn/package.json)
- [packages/shadcn/tsconfig.json](https://github.com/blade47/fumadocs/blob/main/packages/shadcn/tsconfig.json)
- [packages/shadcn/src/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/shadcn/src/index.ts)
- [packages/radix-ui/registry/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/registry/index.ts)
- [packages/base-ui/registry/index.ts](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/registry/index.ts)
- [packages/preview/waku.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/waku.config.ts)
- [packages/radix-ui/src/tailwind/typography.ts](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/src/tailwind/typography.ts)
- [packages/shadcn/src/types.ts](https://github.com/blade47/fumadocs/blob/main/packages/shadcn/src/types.ts)
- [packages/radix-ui/css/preset-legacy.css](https://github.com/blade47/fumadocs/blob/main/packages/radix-ui/css/preset-legacy.css)
- [packages/preview/src/lib/cn.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/lib/cn.ts)
- [packages/story/src/utils/cn.ts](https://github.com/blade47/fumadocs/blob/main/packages/story/src/utils/cn.ts)
- [packages/preview/src/lib/source/config.ts](https://github.com/blade47/fumadocs/blob/main/packages/preview/src/lib/source/config.ts)
- [packages/base-ui/src/utils/cn.ts](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/utils/cn.ts)
- [packages/tailwind/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/main/packages/tailwind/tsdown.config.ts)
- [packages/openapi/src/utils/cn.ts](https://github.com/blade47/fumadocs/blob/main/packages/openapi/src/utils/cn.ts)
- [packages/radix-ui/src/utils/cn.ts](https://github.com/blade47/fumadocs/blob/main/packages/shadcn/src/utils.ts)
- [packages/base-ui/src/tailwind/typography.ts](https://github.com/blade47/fumadocs/blob/main/packages/base-ui/src/tailwind/typography.ts)
The Shadcn Integration component is a specialized subsystem designed to bridge the gap between structured component registries and documentation delivery. It enables developers to generate manual installation snippets for components, configurations, and CSS rules defined in `shadcn` registries, specifically tailored for display within a documentation interface.
The core design principle is the programmatic transformation of raw registry metadata into user-friendly content. By parsing registry JSON files, the system extracts critical setup information—such as dependencies, environment variables, CSS configurations, and file contents—and serializes them into a standardized format (`ManualInstallationSnippet`) that documentation UI components can easily consume and render.
This subsystem provides a consistent interface for managing library installations, ensuring that documentation remains accurate by dynamically reading the same registry sources that drive the installation CLI. It bridges the gap between raw file-based registries and the interactive, React-based components (RSC) used to showcase these integrations in documentation sites.
## Registry Context Initialization
The integration begins with the `createShadcnDocs` function, which initializes the registry environment. It creates a `RegistryContext` object that maintains state regarding the directory and the path to the `registry.json` file. This context is subsequently bound to the `getManualInstallation` method, ensuring all downstream file operations occur relative to the specific registry provided.
Sources: [packages/shadcn/src/index.ts:15-24](https://github.com/blade47/fumadocs/blob/main/packages/shadcn/src/index.ts#L15-L24)
## Manual Installation Generation Pipeline
The generation of installation instructions is a multi-stage pipeline that converts high-level component requirements into granular code snippets.
1. **Item Collection**: The system performs a breadth-first search starting from the requested registry item, recursively resolving `registryDependencies` to ensure the entire dependency graph is captured.
2. **Data Extraction**: For each collected item, the system traverses its definitions—extracting docs, environment variables, CSS rules, and source files.
3. **Snippet Synthesis**: These assets are mapped to a collection of `ManualInstallationSnippet` objects.
4. **Final Assembly**: Install commands (npm/pnpm/yarn) are generated based on the collected dependencies, and all snippets are combined into a final array for rendering.
```mermaid
flowchart TD
A[Start] --> B[collectItems]
B --> C{Process
Dependencies}
C --> D[Extract Metadata]
D --> E[createInstallSnippets]
E --> F[Return
Snippet List]
```
Sources: [packages/shadcn/src/manual-installation.ts:58-139](https://github.com/blade47/fumadocs/blob/main/packages/shadcn/src/manual-installation.ts#L58-L139), [packages/shadcn/src/manual-installation.ts:141-175](https://github.com/blade47/fumadocs/blob/main/packages/shadcn/src/manual-installation.ts#L141-L175)
## Dependency Resolution Logic
When collecting dependencies, the system maintains separate `Set` instances for `dependencies`, `devDependencies`, and `registryDependencies`. These sets automatically deduplicate entries across the entire resolution tree, preventing redundant command generation.
> [!NOTE]
> During registry dependency collection, if `includeRegistryDependencies` is false, the system stops the recursion but explicitly adds existing dependencies to the `registryDependencies` set, ensuring users are aware of what they need to add.
Sources: [packages/shadcn/src/manual-installation.ts:67-77](https://github.com/blade47/fumadocs/blob/main/packages/shadcn/src/manual-installation.ts#L67-L77)
## CSS Variable and Rule Formatting
The subsystem includes specific logic to format raw CSS input into readable code snippets. It handles three distinct tiers of CSS configuration:
| Kind | Trigger | Formatting |
| :--- | :--- | :--- |
| `cssVars` | `hasCssVars()` returns true | Wraps variables in `:root` or `.dark` blocks |
| `css` | Presence in item | Recursively formats records with indentation support |
| `tailwind` | Presence of config | Serializes TS config objects to JSON string |
> [!CAUTION]
> When formatting CSS rules, the system treats object keys starting with `@` (like `@media`) as containers, triggering recursive calls to `formatCssRules` with increased indentation, while simple strings are treated as property assignments with trailing semicolons.
Sources: [packages/shadcn/src/manual-installation.ts:94-116](https://github.com/blade47/fumadocs/blob/main/packages/shadcn/src/manual-installation.ts#L94-L116), [packages/shadcn/src/manual-installation.ts:264-321](https://github.com/blade47/fumadocs/blob/main/packages/shadcn/src/manual-installation.ts#L264-L321)
## RSC Rendering Integration
The integration exposes a React Server Component (RSC) interface (`Snippet`) designed to render generated snippets. It uses a switcher pattern to select the appropriate UI component:
- **Dependency tabs**: Renders via `CodeBlockTabs` if the snippet type is an installation dependency.
- **Documentation**: Renders simple paragraph content for doc-kind snippets.
- **Code files/others**: Renders via `ServerCodeBlock` with associated `transformerIcon` and `transformerNotationDiff` to ensure proper syntax highlighting and UI cues.
Sources: [packages/shadcn/src/rsc.tsx:28-67](https://github.com/blade47/fumadocs/blob/main/packages/shadcn/src/rsc.tsx#L28-L67)
## API Usage Example
To integrate these capabilities into a documentation service, use the `createShadcnDocs` factory to bind the registry and call the `getManualInstallation` method:
```typescript
import { createShadcnDocs } from '@fumadocs/shadcn';
const registryDocs = createShadcnDocs({
registryPath: './registry.json',
});
// Retrieve snippets for a specific component
const snippets = await registryDocs.getManualInstallation({
name: 'button',
includeRegistryDependencies: true,
});
```
Sources: [packages/shadcn/src/index.ts:15-24](https://github.com/blade47/fumadocs/blob/main/packages/shadcn/src/index.ts#L15-L24)
## Related
- [[Command Line Interface]]
- [[Layout System]]
---