# Documentation
> Complete documentation for Large Language Models
---
## Document: Writing
A guide to writing documentation in Zudoku using Markdown and MDX.
URL: /docs/writing
# Writing
Get started with creating rich documentation in Zudoku using Markdown and MDX. This guide covers the
essentials to help you begin documenting your project.
## Quick Start
1. **Create a markdown file** in your `pages` directory
2. **Add frontmatter** with title and metadata
3. **Configure navigation** to make it discoverable
4. **Write content** using Markdown or MDX
### Basic Document Structure
```md
---
title: My Document
sidebar_icon: file-text
---
Your content goes here using standard Markdown syntax.
```
## Adding to Navigation
To make your documentation discoverable, add it to the navigation configuration. Documents are
referenced by their file path:
```ts title="zudoku.config.ts"
const config = {
navigation: [
{
type: "doc",
file: "my-document",
label: "My Document",
},
],
};
```
Learn more about configuring navigation at
[Navigation → Documents](/docs/configuration/navigation#type-doc).
## File Organization
Organize your documentation files in logical directories:
```
pages/
├── getting-started/
│ ├── installation.md
│ └── quick-start.md
├── guides/
│ ├── authentication.md
│ └── deployment.md
└── api/
└── reference.md
```
## What's Next?
Explore the detailed guides to enhance your documentation:
- **[Markdown Overview](/docs/markdown/overview)** - Complete markdown syntax reference
- **[Frontmatter](/docs/markdown/frontmatter)** - Document metadata and configuration
- **[MDX](/docs/markdown/mdx)** - Interactive components in markdown
- **[Admonitions](/docs/markdown/admonitions)** - Callouts and alerts
- **[Code Blocks](/docs/markdown/code-blocks)** - Syntax highlighting and features
---
## Document: Quickstart
Get started with Zudoku by creating a new Zudoku app using the `create-zudoku` tool.
URL: /docs/quickstart
# Quickstart
Ready to build beautiful API documentation and developer portals in minutes? Zudoku's CLI tool will
have you up and running with a modern, customizable site that your developers will love.
## Prerequisites
- **Node.js** `22.12.0+` (or `20.19+`) - [Download here](https://nodejs.org/)
- A terminal or command prompt
- Your favorite code editor
## Getting Started
1. **Create your new Zudoku project:**
```bash
npm create zudoku@latest
```
The CLI will walk you through setting up your project with interactive prompts. Choose from
templates like API documentation, developer portals, or start from scratch.
1. **Navigate to your project:**
```bash
cd your-project-name
```
1. **Launch the development server:**
```bash
npm run dev
```
🎉 **That's it!** Your Zudoku site is now running at `http://localhost:3000`. You'll see hot
reloading as you make changes, so you can iterate quickly.
1. **What's next?**
- Customize your [theme and colors](./customization/colors-theme.mdx)
- Add your [OpenAPI specifications](./configuration/api-reference.md)
- Set up [authentication](./configuration/authentication.md)
- [Deploy your site](./deployment.md) when you're ready to go live
## 🚀 Pro Tips
- Use `npm run build` to create a production build
- Check out our [examples](https://github.com/zuplo/zudoku/tree/main/examples) for inspiration
- Join our [Discord community](https://discord.zudoku.dev) for support and tips
---
## Document: Zudoku Plugins
URL: /docs/plugins
# Zudoku Plugins
Zudoku can be extended using plugins.
---
## Document: Deploying Zudoku
URL: /docs/deployment
# Deploying Zudoku
Once you are happy with your Zudoku powered documentation and ready to push your docs to production
you will need to deploy it to your own server, or a hosted service of your choice.
## Build locally
Zudoku can produce a build of static HTML, JavaScript and CSS files that you can deploy directly to
your own server.
To prepare the files you need to upload to your server, you will need to use the build command.
```
npm run build
```
Once complete, you will see a new `dist` folder in the root of your project that includes the files
you need to upload.
---
## Document: Custom Plugins
URL: /docs/custom-plugins
# Custom Plugins
Zudoku is highly extensible. You can create custom plugins to add new functionality to your
documentation site. This guide will show you how to create and use plugins in your Zudoku
configuration.
## Plugin Types
All plugins in Zudoku must implement the `ZudokuPlugin` type, which is a union of these plugin
interfaces:
- **CommonPlugin**: Basic plugin with initialization, head elements, and MDX component customization
- **ProfileMenuPlugin**: Add custom items to the profile menu
- **NavigationPlugin**: Define custom routes and sidebar items
- **ApiIdentityPlugin**: Provide API identities for testing
- **SearchProviderPlugin**: Implement custom search functionality
- **EventConsumerPlugin**: Handle custom events
- **TransformConfigPlugin**: Modify configuration at build-time
You can find all available plugin interfaces in the
[Zudoku source code](https://github.com/zuplo/zudoku/blob/main/packages/zudoku/src/lib/core/plugins.ts).
## Defining Plugins
You can define plugins in your Zudoku configuration using objects with explicit type declarations:
### Common Plugin Example
```tsx
import { ZudokuPlugin } from "zudoku";
const commonPlugin: ZudokuPlugin = {
initialize: async (context) => {
// Initialization logic
},
getHead: () => ,
getMdxComponents: () => ({
// Custom MDX components
}),
};
const config: ZudokuConfig = {
// ... other config
plugins: [commonPlugin],
};
```
### API Identity Plugin Example
```tsx
import { ZudokuPlugin, ApiIdentity } from "zudoku";
const apiIdentityPlugin: ZudokuPlugin = {
getIdentities: async (context) => {
return [
{
label: "Test User",
id: "test-user",
authorizeRequest: (request: Request) => {
request.headers.set("Authorization", "Bearer test-token");
return request;
},
},
] as ApiIdentity[];
},
};
// In your zudoku.config.tsx
const config: ZudokuConfig = {
// ... other config
plugins: [apiIdentityPlugin],
};
```
## Example Implementations
Here are some common plugin implementations:
### Google Tag Manager
Below is a sample of adding the necessary scripts for GTM, but this could apply to any tag manager
or tracking script.
```tsx
import { ZudokuPlugin } from "zudoku";
const commonPlugin: ZudokuPlugin = {
getHead: () => {
return (
);
},
};
```
#### Tracking `page_view` Events
Zudoku is a single page application so typical `page_view` events are not captured by most analytics
scripts or tag managers. Instead, you must listen to the `location` [event](./extending/events.md)
with a plugin and log navigation changes in code.
```tsx
import { ZudokuPlugin, ZudokuEvents } from "zudoku";
const navigationLoggerPlugin: ZudokuPlugin = {
events: {
location: ({ from, to }) => {
if (!from) return;
window.dataLayer.push({
event: "page_view",
page_path: to.pathname,
page_title: document.title,
page_location: window.location.href,
});
},
},
};
```
If you are using TypeScript, you will also need to add the following type declaration to the file
this plugin is declared
```ts
declare global {
interface Window {
dataLayer: Record[];
}
}
```
### Navigation Plugin
```tsx
import { ZudokuPlugin, RouteObject } from "zudoku";
const navigationPlugin: ZudokuPlugin = {
getRoutes: (): RouteObject[] => {
return [
{
path: "/custom",
element: ,
},
];
},
getNavigation: async (path: string, context) => {
// Return custom navigation items
return [
{
type: "link",
to: "/custom",
label: "Custom Page",
},
];
},
};
```
### Wrapping Routes with Context or Layout
You can wrap your plugin's routes with a context provider or custom layout using React Router's
nested route pattern. The parent route renders an `` where child routes will appear.
```tsx
import { createContext, useContext } from "react";
import type { ZudokuPlugin, RouteObject } from "zudoku";
import { Outlet } from "zudoku/router";
const MyContext = createContext("value");
const pluginWithContext: ZudokuPlugin = {
getRoutes: () => [
{
element: (
),
children: [
{ path: "/custom", element: },
{ path: "/custom/nested", element: },
],
},
],
};
```
All child routes will have access to `MyContext`. This pattern works for any wrapper including
layouts, error boundaries, or data providers.
### Dropdown Navigation Plugin
```tsx
import { ZudokuPlugin, RouteObject } from "zudoku";
import { UserIcon } from "zudoku/icons";
const AccountPageNavItemPlugin: ZudokuPlugin = {
getRoutes: (): RouteObject[] => {
return [
{
path: "/account",
element: , // This is a custom page
},
];
},
getProfileMenuItems: () => {
return [
{
label: "Account",
path: "/account",
category: "middle",
icon: UserIcon,
},
];
},
};
```
### Event Consumer Plugin
```tsx
import { ZudokuPlugin } from "zudoku";
const eventConsumerPlugin: ZudokuPlugin = {
events: {
location: ({ from, to }) => {
if (!from) {
console.log(`Initial navigation to: ${to.pathname}`);
} else {
console.log(`Navigation from ${from.pathname} to ${to.pathname}`);
}
},
},
};
```
### Transform Config Plugin
The `transformConfig` hook allows plugins to modify the Zudoku configuration at build-time. This is
useful for dynamically adding navigation items, modifying theme settings, or adjusting any other
configuration based on external data or conditions.
```tsx
import { ZudokuPlugin } from "zudoku";
const transformConfigPlugin: ZudokuPlugin = {
transformConfig: ({ config, merge }) => {
// Option 1: Use merge helper for deep merging
return merge({
slots: {
"head-navigation-start": () => Pricing,
},
});
// Option 2: Manual spread for full control
return {
...config,
navigation: [
...(config.navigation ?? []),
{
type: "link",
label: "System Status",
to: "https://status.example.com",
icon: "activity",
},
],
};
},
};
```
The `transformConfig` function receives an object with:
- **config**: The current Zudoku configuration object
- **merge**: A helper function that deep merges a partial config with the current config
The function must return a full configuration object (either via `merge()` or manual spreading), or
`void` to make no changes. The hook can also be async.
---
## Document: x-zudoku-playground-enabled
URL: /docs/openapi-extensions/x-zudoku-playground-enabled
# x-zudoku-playground-enabled
Use `x-zudoku-playground-enabled` to show or hide the interactive API playground for a specific
operation. By default, the playground is shown for all operations unless globally disabled via the
[`disablePlayground`](/docs/configuration/api-reference) option.
## Location
The extension is added at the **Operation Object** level.
| Option | Type | Description |
| ----------------------------- | --------- | ----------------------------------------------- |
| `x-zudoku-playground-enabled` | `boolean` | Show (`true`) or hide (`false`) the playground. |
| `x-explorer-enabled` | `boolean` | Alias for `x-zudoku-playground-enabled`. |
Both extensions are checked — if either is explicitly set, that value is used. If neither is set,
the playground visibility falls back to the global `disablePlayground` configuration.
## Example
```yaml
paths:
/users:
get:
summary: List users
x-zudoku-playground-enabled: true
responses:
"200":
description: Successful response
/webhooks/trigger:
post:
summary: Trigger webhook
x-zudoku-playground-enabled: false
responses:
"200":
description: Accepted
```
In this example, `List users` shows the playground while `Trigger webhook` hides it regardless of
the global setting.
---
## Document: x-zudoku-collapsible
URL: /docs/openapi-extensions/x-zudoku-collapsible
# x-zudoku-collapsible
Use `x-zudoku-collapsible` to control whether a tag category can be collapsed or expanded by users
in the API navigation sidebar.
## Location
The extension is added at the **Tag Object** level.
| Option | Type | Description |
| ---------------------- | --------- | ------------------------------------------------------------------- |
| `x-zudoku-collapsible` | `boolean` | Whether the tag can be collapsed. Defaults to `true` (collapsible). |
When set to `false`, the tag section remains permanently expanded and users cannot toggle it.
## Example
```yaml
tags:
- name: Core API
x-zudoku-collapsible: false
x-zudoku-collapsed: false
- name: Utilities
x-zudoku-collapsible: true
```
In this example, `Core API` is always expanded and cannot be collapsed. `Utilities` can be toggled
by users.
## Related
- [`x-zudoku-collapsed`](./x-zudoku-collapsed) — control the initial collapsed state
---
## Document: x-zudoku-collapsed
URL: /docs/openapi-extensions/x-zudoku-collapsed
# x-zudoku-collapsed
Use `x-zudoku-collapsed` to control whether a tag category starts expanded or collapsed in the API
navigation sidebar.
## Location
The extension is added at the **Tag Object** level.
| Option | Type | Description |
| -------------------- | --------- | ----------------------------------------------------------------- |
| `x-zudoku-collapsed` | `boolean` | Whether the tag starts collapsed. Defaults to `true` (collapsed). |
When not set, the collapsed state falls back to the inverse of the
[`expandAllTags`](/docs/configuration/api-reference) option in the API configuration.
## Example
```yaml
tags:
- name: Getting Started
x-zudoku-collapsed: false
- name: Advanced
x-zudoku-collapsed: true
```
In this example, `Getting Started` is expanded by default while `Advanced` starts collapsed.
## Related
- [`x-zudoku-collapsible`](./x-zudoku-collapsible) — control whether a tag section can be collapsed
at all
---
## Document: x-tagGroups
URL: /docs/openapi-extensions/x-tag-groups
# x-tagGroups
Use `x-tagGroups` to organize tags into named groups in the API navigation sidebar. Without this
extension, tags appear as a flat list. With tag groups, related tags are nested under group
headings.
## Location
The extension is added at the **Root Object** level — the outermost level of the OpenAPI
description.
| Option | Type | Description |
| ------------- | -------------------- | ------------------------------------------ |
| `x-tagGroups` | `[Tag Group Object]` | Array of tag groups for navigation layout. |
## Tag Group Object
| Property | Type | Required | Description |
| -------- | ---------- | -------- | -------------------------------------------- |
| `name` | `string` | Yes | Display name for the group in the sidebar. |
| `tags` | `[string]` | Yes | Array of tag names to include in this group. |
## Example
```yaml
openapi: 3.1.0
info:
title: Shipping API
version: 1.0.0
tags:
- name: Packages
- name: Parcels
- name: Letters
- name: Tracking
- name: Billing
x-tagGroups:
- name: Shipment
tags:
- Packages
- Parcels
- Letters
- name: Management
tags:
- Tracking
- Billing
```
This produces a sidebar like:
```
Shipment
├── Packages
├── Parcels
└── Letters
Management
├── Tracking
└── Billing
```
Tags not included in any group are appended after the defined groups.
---
## Document: x-mcp
URL: /docs/openapi-extensions/x-mcp
# x-mcp
Use `x-mcp` to document [MCP](https://modelcontextprotocol.io/) (Model Context Protocol) server
metadata in your OpenAPI description. This extension describes the MCP server capabilities, tools,
resources, and prompts at the document root.
:::tip
Support for rendering `x-mcp` in Zudoku is currently in development. For now, if you want to mark
individual operations as MCP endpoints with full UI support, use the
[`x-mcp-server` extension](./x-mcp-server).
:::
## Location
The `x-mcp` extension is added at the **Root Object** level — the outermost level of the OpenAPI
description.
| Option | Type | Description |
| ------- | ---------- | ---------------------------------------- |
| `x-mcp` | MCP Object | MCP server description and configuration |
## MCP Object
| Property | Type | Required | Description |
| ----------------- | --------------------- | -------- | --------------------------------------------------------------------------------------------- |
| `protocolVersion` | `string` | Yes | The MCP protocol version supported by the server. |
| `servers` | `[Server Object]` | No | A list of server objects used to add one or more target endpoints for the MCP server. |
| `capabilities` | `Capabilities Object` | No | Server capabilities including supported features like logging, prompts, resources, and tools. |
| `tools` | `[Tool Object]` | No | Array of tools provided by the MCP server. |
| `resources` | `[Resource Object]` | No | Array of resources provided by the MCP server. |
| `prompts` | `[Prompt Object]` | No | Array of prompts provided by the MCP server. |
## Capabilities Object
| Property | Type | Description |
| ----------- | -------- | --------------------------------------------------------------------------------------------------- |
| `logging` | `object` | Logging capabilities configuration. Empty object indicates basic logging support. |
| `prompts` | `object` | Prompt capabilities configuration with optional `listChanged` boolean property. |
| `resources` | `object` | Resource capabilities configuration with optional `subscribe` and `listChanged` boolean properties. |
| `tools` | `object` | Tool capabilities configuration with optional `listChanged` boolean property. |
## Tool Object
| Property | Type | Required | Description |
| -------------- | -------------------- | -------- | ------------------------------------------------------------------------------- |
| `name` | `string` | Yes | The name of the tool. |
| `title` | `string` | No | Title of the tool. |
| `description` | `string` | Yes | Description of what the tool does. |
| `tags` | `[string]` | No | Tags for the tool. |
| `inputSchema` | `object` | No | JSON Schema describing the expected input parameters for the tool. |
| `outputSchema` | `object` or `string` | No | JSON Schema describing the tool's output, or a reference to a schema component. |
| `security` | `[object]` | No | Security requirements for the tool, following OpenAPI security scheme format. |
## Resource Object
| Property | Type | Required | Description |
| ------------- | -------- | -------- | ---------------------------------------- |
| `name` | `string` | Yes | The name of the resource. |
| `description` | `string` | No | Description of the resource. |
| `uri` | `string` | No | URI template for accessing the resource. |
| `mimeType` | `string` | No | MIME type of the resource content. |
## Prompt Object
| Property | Type | Required | Description |
| ------------- | ------------------- | -------- | ---------------------------------- |
| `name` | `string` | Yes | The name of the prompt. |
| `title` | `string` | No | Title of the prompt. |
| `description` | `string` | No | Description of the prompt. |
| `arguments` | `[Argument Object]` | No | Array of arguments for the prompt. |
### Argument Object
| Property | Type | Required | Description |
| ------------- | --------- | -------- | --------------------------------- |
| `name` | `string` | Yes | The name of the argument. |
| `description` | `string` | No | Description of the argument. |
| `required` | `boolean` | No | Whether the argument is required. |
## Example
The following example shows an OpenAPI description with an `x-mcp` extension that defines an MCP
server with OAuth2 security, multiple tools, and schema components:
```yaml
openapi: 3.2.0
info:
version: 1.0.0
title: API Clients MCP
license:
name: MIT
servers:
- url: http://localhost:8080/mcp
paths: {}
x-mcp:
protocolVersion: "2025-06-18"
capabilities:
logging: {}
prompts:
listChanged: true
resources:
subscribe: true
tools:
listChanged: true
tools:
- name: clients/get
description: Get a list of clients with all scopes in a service domain.
inputSchema:
type: object
properties:
clientId:
type: string
description: The ID of the client to get.
outputSchema:
$ref: "#/components/schemas/Client"
security:
- OAuth2:
- read
- name: clients/list
description: Get a list of clients with all scopes in a service domain.
inputSchema:
type: object
properties:
paginationToken:
type: string
description: The pagination token to get the next page of clients.
outputSchema:
type: object
properties:
clients:
type: array
items:
$ref: "#/components/schemas/Client"
paginationToken:
type: string
description: The pagination token to get the next page of clients.
resources: []
components:
securitySchemes:
OAuth2:
type: oauth2
flows:
clientCredentials:
tokenUrl: http://localhost:8080/mcp/token
scopes:
read: Read access
write: Write access
schemas:
Client:
type: object
properties:
clientId:
type: number
description: The ID of the client.
scopes:
type: array
items:
type: string
description: The scopes of the client.
required:
- clientId
- scopes
```
---
## Document: x-mcp-server
URL: /docs/openapi-extensions/x-mcp-server
# x-mcp-server
Use `x-mcp-server` to mark an individual OpenAPI operation as an
[MCP](https://modelcontextprotocol.io/) (Model Context Protocol) endpoint. When Zudoku detects this
extension, it replaces the standard request/response view with a dedicated MCP card showing the
endpoint URL, a copy button, and tabbed installation instructions for popular AI clients.
:::note
The `x-mcp-server` extension is applied at the **operation level** to mark specific endpoints. If
you want to describe an entire MCP server at the root level of your OpenAPI document, see the
[`x-mcp` extension](./x-mcp).
:::
## Location
The `x-mcp-server` extension is added at the **Operation Object** level.
| Option | Type | Description |
| -------------- | -------------------------------- | ---------------------------------------------- |
| `x-mcp-server` | `boolean` or `MCP Server Object` | Marks the operation as an MCP server endpoint. |
## MCP Server Object
When using the object form, the following properties are available:
| Property | Type | Required | Description |
| --------- | --------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `name` | `string` | No | Display name used in the generated client configuration snippets. Falls back to the operation `summary`, then `"mcp-server"` |
| `version` | `string` | No | Version metadata |
| `tools` | `[Tool Object]` | No | Array of tools provided by the MCP server |
Each item in the `tools` array:
| Property | Type | Required | Description |
| ------------- | -------- | -------- | ------------------------------- |
| `name` | `string` | Yes | Tool name |
| `description` | `string` | No | Human-readable tool description |
## MCP URL resolution
The displayed MCP URL is constructed from the **server URL** of the API and the **path** of the
operation. The server URL comes from the OpenAPI `servers` array (or the operation-level `servers`
override if present).
## Examples
### Boolean shorthand
Use `true` to enable MCP UI without specifying metadata. The operation's `summary` is used as the
server name.
```yaml
paths:
/mcp:
post:
summary: My MCP Server
x-mcp-server: true
responses:
"200":
description: MCP response
```
### Object form
```yaml
paths:
/mcp:
post:
summary: My MCP Server
x-mcp-server:
name: my-mcp-server
version: 1.0.0
tools:
- name: search_docs
description: Search the documentation
- name: get_page
description: Retrieve a specific documentation page
responses:
"200":
description: MCP response
```
## Generated UI
When detected, the operation page shows:
- **MCP Endpoint card** with the full URL and a copy button
- **AI Tool Configuration** tabs with setup instructions for:
- **Claude** — add via Connectors UI or `claude mcp add` CLI command
- **ChatGPT** — app setup via Settings → Apps → Advanced Settings
- **Cursor** — `mcp.json` configuration (global or project-level)
- **VS Code** — `.vscode/mcp.json` with native HTTP transport for GitHub Copilot
- **Generic** — standard `mcp.json` format compatible with most MCP clients
The standard method badge, request body, parameters, and sidecar panels are hidden for MCP
endpoints.
For a full walkthrough including Zudoku configuration, see the
[Documenting MCP Servers guide](/docs/guides/mcp-servers).
---
## Document: x-displayName
URL: /docs/openapi-extensions/x-display-name
# x-displayName
Use `x-displayName` to override the display label for a tag in the API navigation and documentation.
By default, Zudoku uses the tag's `name` field. This extension lets you set a different
human-friendly label without changing the tag name used for grouping operations.
## Location
The extension is added at the **Tag Object** level.
| Option | Type | Description |
| --------------- | -------- | ------------------------------------------------------ |
| `x-displayName` | `string` | Custom display name shown in the sidebar and headings. |
## Example
```yaml
tags:
- name: ai-ops
description: AI-powered operations
x-displayName: AI Operations
- name: user-mgmt
description: User management endpoints
x-displayName: User Management
```
Without `x-displayName`, the sidebar would show `ai-ops` and `user-mgmt`. With it, the sidebar
displays `AI Operations` and `User Management` instead.
---
## Document: x-code-samples
URL: /docs/openapi-extensions/x-code-samples
# x-code-samples
Use `x-code-samples` (or `x-codeSamples`) to provide custom code snippets for an API operation. When
present, these samples appear in the sidecar panel alongside the auto-generated request examples.
## Location
The extension is added at the **Operation Object** level.
| Option | Type | Description |
| ---------------- | ---------------------- | ----------------------------- |
| `x-code-samples` | `[Code Sample Object]` | Array of custom code samples. |
| `x-codeSamples` | `[Code Sample Object]` | Alias for `x-code-samples`. |
## Code Sample Object
| Property | Type | Required | Description |
| -------- | -------- | -------- | ---------------------------------------------------- |
| `lang` | `string` | Yes | Language identifier used for syntax highlighting. |
| `label` | `string` | No | Display label for the tab. Defaults to `lang` value. |
| `source` | `string` | Yes | The code snippet content. |
## Example
```yaml
paths:
/users:
get:
summary: List users
x-code-samples:
- lang: curl
label: cURL
source: |
curl -X GET https://api.example.com/users \
-H "Authorization: Bearer $TOKEN"
- lang: python
label: Python
source: |
import requests
response = requests.get(
"https://api.example.com/users",
headers={"Authorization": f"Bearer {token}"},
)
- lang: javascript
label: JavaScript
source: |
const response = await fetch("https://api.example.com/users", {
headers: { Authorization: `Bearer ${token}` },
});
responses:
"200":
description: Successful response
```
---
## Document: Markdown
Comprehensive guide to using Markdown and MDX in Zudoku, including formatting, frontmatter, syntax highlighting, tables, lists, task lists, collapsible sections, and advanced documentation features.
URL: /docs/markdown/overview
# Markdown
Zudoku supports
[GitHub Flavored Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax)
(GFM) with additional features for creating rich documentation.
## Basic Formatting
### Headers
Use `#` to create headers. The number of `#` symbols determines the header level:
```md
# H1 Header
## H2 Header
### H3 Header
#### H4 Header
##### H5 Header
###### H6 Header
```
### Text Formatting
```mdx
**Bold text**
_Italic text_
~~Strikethrough text~~
`Inline code`
```
**Bold text**
_Italic text_
~~Strikethrough text~~
`Inline code`
### Lists
**Unordered lists:**
```md
- Item 1
- Item 2
- Nested item
- Another nested item
```
**Ordered lists:**
```md
1. First item
2. Second item
1. Nested item
2. Another nested item
```
See list examples
**Unordered list:**
- Item 1
- Item 2
- Nested item
- Another nested item
**Ordered list:**
1. First item
2. Second item
1. Nested item
2. Another nested item
### Links and Images
```md
[Link text](https://example.com)

```
See link and image examples
[Link text](https://example.com)

### Tables
```md
| Header 1 | Header 2 | Header 3 |
| -------- | -------- | -------- |
| Cell 1 | Cell 2 | Cell 3 |
| Cell 4 | Cell 5 | Cell 6 |
```
See table example
| Header 1 | Header 2 | Header 3 |
| -------- | -------- | -------- |
| Cell 1 | Cell 2 | Cell 3 |
| Cell 4 | Cell 5 | Cell 6 |
### Blockquotes
```md
> This is a blockquote
>
> It can span multiple lines
```
See blockquote example
> This is a blockquote
>
> It can span multiple lines
## Frontmatter
Frontmatter allows you to configure page metadata using YAML at the beginning of your markdown
files:
```md
---
title: My Page Title
description: Page description for SEO
sidebar_icon: book
category: Getting Started
---
Your markdown content starts here...
```
Common frontmatter properties include `title`, `description`, `sidebar_icon`, and `category`. For a
complete list of supported properties, see the [Frontmatter documentation](./frontmatter).
## MDX Support
Zudoku supports [MDX](./mdx), allowing you to use JSX components within your markdown:
```mdx title=my-page.mdx
import MyCustomComponent from "./MyCustomComponent";
# Regular Markdown
This is regular markdown content.
You can mix markdown and JSX seamlessly.
```
MDX enables you to create interactive documentation with custom React components. Learn more in the
[MDX documentation](./mdx).
## Syntax Highlighting
Zudoku uses [Shiki](https://shiki.style/) for syntax highlighting in code blocks:
````md
```javascript
function greet(name) {
console.log(`Hello, ${name}!`);
}
```
````
**Advanced features:**
- Line highlighting: `{1,3-5}`
- Word highlighting: `/keyword/`
- Line numbers: `showLineNumbers`
- Titles: `title="filename.js"`
````
```tsx {4-5} /useState/ title="Counter.tsx" showLineNumbers
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return ;
}
```
````
See advanced features example
```tsx {4-5} /useState/ title="Counter.tsx" showLineNumbers
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return ;
}
```
For complete syntax highlighting documentation, see [Code Blocks](./code-blocks).
## Additional Features
Zudoku also supports:
- [Admonitions](./admonitions) - Callout boxes for notes, warnings, and tips
- Task lists with checkboxes
- Automatic link detection
### Task Lists
```md
- [x] Completed task
- [ ] Incomplete task
- [ ] Another task
```
See task list example
- [x] Completed task
- [ ] Incomplete task
- [ ] Another task
### Collapsible Sections
You can create collapsible content using HTML `` and `` tags:
```html
Click to expand
This content is hidden by default and can be expanded by clicking the summary. You can include any
markdown content here: - Lists - **Bold text** - Code blocks - Images
```
Click to expand
This content is hidden by default and can be expanded by clicking the summary.
You can include any markdown content here:
- Lists
- **Bold text**
- Code blocks
- Images
---
## Document: MDX
Learn how to use MDX in Zudoku to create rich documentation pages with markdown and custom React components.
URL: /docs/markdown/mdx
# MDX
Zudoku supports MDX files for creating rich content pages. MDX is a markdown format that allows you
to include JSX components in your markdown files.
## Getting Started
To use MDX in your documentation, simply create files with the `.mdx` extension instead of `.md`.
These files work exactly like regular markdown files but with all MDX features unlocked - you can
write normal markdown content and add JSX components whenever needed.
```
docs/
├── my-page.md # Regular markdown
├── my-mdx-page.mdx # MDX with JSX support
```
## Custom Components
Zudoku supports the use of custom components in your MDX files. This allows you to create reusable
components that can be used across multiple pages.
You can create a custom component in your project and reference it in the
[Zudoku Configuration](./overview.md) file.
For example, create the `` component in a file called `MyCustomComponent.tsx`
in the `src` directory at the root of your project.
```tsx
export default function MyCustomComponent() {
return
My Custom Component
;
}
```
In [Zudoku Configuration](./overview.md) you will need to import the component and add it to the
`customComponents` option in the configuration.
```ts title=zudoku.config.ts
import MyCustomComponent from "./src/MyCustomComponent";
const config: ZudokuConfig = {
// ...
mdx: {
components: {
MyCustomComponent,
},
},
// ...
};
export default config;
```
## JSX in Headings
JSX components in headings render in both the sidebar navigation and table of contents:
```mdx
# My Page New
```
Components must be registered via [`mdx.components`](#custom-components) to work in headings.
---
## Document: Frontmatter
Learn how to use YAML frontmatter in Zudoku markdown files to customize page titles, descriptions, navigation, and other document properties.
URL: /docs/markdown/frontmatter
# Frontmatter
Frontmatter is metadata written in [YAML](https://yaml.org/) format at the beginning of markdown
files, enclosed between triple dashes (`---`). It allows you to configure various aspects of your
pages without affecting the visible content.
In Zudoku, frontmatter enables you to customize page titles, descriptions, navigation settings, and
other document properties. Here are all the supported properties:
## Properties
### `title`
Sets the page title that appears in the browser tab and as the document title.
```md
---
title: My Page Title
---
```
### `description`
Provides a description for the page, which can be used for SEO and content summaries.
```md
---
description: This page explains how to use Zudoku's markdown features.
---
```
### `category`
Assigns the page to a specific category for organizational purposes. This will be shown above the
main heading of the document.
```md
---
category: Getting Started
---
```
### `sidebar_label`
Sets a custom label for the page in the sidebar navigation, allowing you to use a shorter or
different title than the main page title.
```md
---
title: My Very Long Documentation Page Title
sidebar_label: Short Title
---
```
The legacy name `navigation_label` is also supported but `sidebar_label` is preferred.
### `sidebar_icon`
Specifies a [Lucide icon](https://lucide.dev/icons) to display next to the page in the sidebar
navigation.
```md
---
sidebar_icon: compass
---
```
The legacy name `navigation_icon` is also supported but `sidebar_icon` is preferred.
### `navigation_display`
Specifies the display property of the navigation item. See the
[Navigation guide](/docs/configuration/navigation#display-control)
```md
---
navigation_display: auth
---
```
### `toc`
Controls whether the table of contents is displayed for the page. Set to `false` to hide the table
of contents.
```md
---
toc: false
---
```
### `fullWidth`
Removes the table of contents sidebar and lets the page content span the full available width. When
enabled, the table of contents is still accessible via an "On this page" toggle in the page header
(unless `toc: false` is also set, in which case it is hidden entirely).
```md
---
fullWidth: true
---
```
| `fullWidth` | `toc` | Result |
| ----------- | ------- | --------------------------------------------------------------- |
| `false` | `true` | TOC shown in the sidebar (default). |
| `false` | `false` | TOC hidden; content keeps its standard width. |
| `true` | `true` | Content spans full width; TOC is available via a toggle button. |
| `true` | `false` | Content spans full width; TOC is not available at all. |
### `disable_pager`
Controls whether the previous/next page navigation is displayed at the bottom of the page. Set to
`true` to disable it.
```md
---
disable_pager: true
---
```
### `showLastModified`
Controls whether the last modified date is displayed for this page. Can be used to override the
[default option](/docs/configuration/docs#showlastmodified).
```md
---
showLastModified: false
---
```
### `draft`
Marks a document as a draft. Draft documents are only visible when running in development mode and
are excluded from production builds. This is useful for working on content that isn't ready to be
published.
```md
---
draft: true
---
```
:::info
When `draft: true` is set:
- The document will be visible when running `zudoku dev`
- The document will be excluded from builds created with `zudoku build`
- The document won't appear in the navigation or be accessible via URL in production
:::
### `lastModifiedTime`
The last modified timestamp for the page. This property is automatically set by Zudoku during the
build process based on the Git commit history. You generally should not set this manually.
If you need to override the automatically detected date, you can set it explicitly:
```md
---
lastModifiedTime: 2025-11-20T10:30:00.000Z
---
```
::if{mode=opensource}
:::info
For accurate last modified dates in deployment environments, ensure full Git history is available
during builds. See the [Vercel deployment guide](/docs/deploy/vercel#accurate-last-modified-dates)
for configuration details.
:::
::
## Complete Example
Here's an example showing multiple frontmatter properties used together:
```md title=documentation.md
---
title: Advanced Configuration Guide
description: Learn how to configure advanced features in Zudoku
category: Configuration
sidebar_label: Advanced Config
sidebar_icon: settings
toc: true
disable_pager: false
draft: false
---
This page content follows the frontmatter...
```
---
## Document: Code Blocks
Learn how to use code blocks, syntax highlighting, and advanced features like line highlighting and ANSI output in Zudoku Markdown with Shiki.
URL: /docs/markdown/code-blocks
# Code Blocks
Zudoku supports code blocks in Markdown using the [Shiki](https://shiki.style/) syntax highlighting
library.
See examples for all supported languages in the
[Syntax Highlighting](../components/syntax-highlight#supported-languages) section.
## Syntax Highlighting
Code blocks are text blocks wrapped around by strings of 3 backticks. You may check out this
reference for the specifications of MDX.
````md
```js
console.log("Every repo must come with a mascot.");
```
````
The code block above will render as:
```js
console.log("Every repo must come with a mascot.");
```
:::note
You can also use the [`SyntaxHighlight` component](../components/syntax-highlight) to render code
blocks in TypeScript directly.
:::
## Inline Code
You can highlight inline code using either:
Regular backticks without language specification:
```md
`console.log("Hello World")`
```
Result: `console.log("Hello World")`
or with the [tailing curly colon syntax](https://shiki.matsu.io/packages/rehype#inline-code):
```md
`console.log("Hello World"){:js}`
```
Result: `console.log("Hello World"){:js}`
For more details, see the
[Shiki Rehype documentation](https://shiki.style/packages/rehype#inline-code).
You can add a title to code blocks by adding a title attribute after the backticks:
````md
```tsx title="hello.tsx"
console.log("Hello, World!");
```
````
Result:
```tsx title="hello.tsx"
console.log("Hello, World!");
```
For a complete list of supported languages and their aliases, see the
[Shiki Languages documentation](https://shiki.style/languages#bundled-languages).
## Advanced Syntax Highlighting
There are multiple ways to enhance syntax highlighting:
- [Line highlighting](https://shiki.style/packages/transformers#transformermetahighlight)
- [Word highlighting](https://shiki.style/packages/transformers#transformermetawordhighlight)
- Line numbers: `showLineNumbers`
- Title: `title`
Example:
````
```tsx {4-6} /react/ title="Example.tsx" showLineNumbers
import { useEffect } from "react";
function Example() {
useEffect(() => {
console.log("Mounted");
}, []);
return
;
}
```
## Configuration
You can configure syntax highlighting in your `zudoku.config.tsx`:
:::info
Changes to the syntax `highlighting` configuration require a restart of Zudoku to take effect.
:::
```tsx {5-15} title=zudoku.config.ts
import { defaultLanguages, type ZudokuConfig } from "zudoku";
const config: ZudokuConfig = {
// ...
syntaxHighlighting: {
themes: {
light: "vitesse-light",
dark: "vitesse-dark",
},
// Extend default languages with additional ones
// Aliases like "lisp" are resolved automatically
languages: [...defaultLanguages, "rust", "ruby", "php", "powershell"],
},
};
```
For a complete list of available themes and languages, see the list of
[Shiki themes](https://shiki.style/themes) and [Shiki languages](https://shiki.style/languages).
## Default Supported Languages
By default, Zudoku supports the following languages for syntax highlighting:
- Shell - `shellscript`, `bash`, `sh`, `zsh`
- JavaScript/TypeScript - `javascript`, `typescript`, `jsx`, `tsx`
- Data formats - `json`, `jsonc`, `yaml`
- HTML/CSS/XML - `html`, `css`, `xml`
- Markdown - `markdown`, `mdx`
- Python - `python`
- Java - `java`
- Go - `go`
- GraphQL - `graphql`
Additional languages can be added via `syntaxHighlighting.languages` in your config. Languages not
in the list fall back to plain text with a console warning. You can use aliases (e.g. `lisp` for
`common-lisp`) and they will resolve automatically.
## ANSI Code Blocks
You can use the `ansi` language to highlight terminal outputs with ANSI escape sequences. This is
useful for displaying colored terminal output, styled text, and other terminal-specific formatting.
```ansi title="Terminal Output"
[0;32mcolored foreground[0m
[0;1mbold text[0m
[0;2mdimmed text[0m
[0;4munderlined text[0m
[0;7mreversed text[0m
[0;9mstrikethrough text[0m
[0;4;9munderlined + strikethrough text[0m
```
Usage:
````md
```ansi title="Terminal Output"
[0;32mcolored foreground[0m
[0;1mbold text[0m
[0;2mdimmed text[0m
[0;4munderlined text[0m
[0;7mreversed text[0m
[0;9mstrikethrough text[0m
[0;4;9munderlined + strikethrough text[0m
```
````
For more details on ANSI highlighting, see the
[Shiki documentation](https://shiki.style/languages#ansi).
---
## Document: Admonitions
Learn how to use admonitions (callouts) in Markdown, including syntax, types, titles, and formatting tips for compatibility with Prettier.
URL: /docs/markdown/admonitions
# Admonitions
In addition to the basic Markdown syntax, we have a special admonitions syntax by wrapping text with
a set of 3 colons, followed by a label denoting its type.
:::note
Admonitions are also commonly referred to as "Callouts". For programmatic usage, see the
[Callout component](/docs/components/callout).
:::
Example:
```markdown
:::note
Some **content** with _Markdown_ `syntax`. Check [this `api`](#).
:::
:::tip
Some **content** with _Markdown_ `syntax`. Check [this `api`](#).
:::
:::info
Some **content** with _Markdown_ `syntax`. Check [this `api`](#).
:::
:::warning
Some **content** with _Markdown_ `syntax`. Check [this `api`](#).
:::
:::danger
Some **content** with _Markdown_ `syntax`. Check [this `api`](#).
:::
```
:::note
Some **content** with _Markdown_ `syntax`. Check [this `api`](#).
:::
:::tip
Some **content** with _Markdown_ `syntax`. Check [this `api`](#).
:::
:::info
Some **content** with _Markdown_ `syntax`. Check [this `api`](#).
:::
:::warning
Some **content** with _Markdown_ `syntax`. Check [this `api`](#).
:::
:::danger
Some **content** with _Markdown_ `syntax`. Check [this `api`](#).
:::
## Additional types
Beyond the standard severity types, the following themed variants are available with their own color
and icon:
```markdown
:::sparkles
For new features or AI-powered functionality.
:::
:::rocket
For getting-started or launch-related content.
:::
:::settings
For configuration sections.
:::
:::zap
For performance tips.
:::
:::lock
For authentication and security notes.
:::
:::megaphone
For announcements.
:::
```
:::sparkles
For new features or AI-powered functionality.
:::
:::rocket
For getting-started or launch-related content.
:::
:::settings
For configuration sections.
:::
:::zap
For performance tips.
:::
:::lock
For authentication and security notes.
:::
:::megaphone
For announcements.
:::
## With title
You can also add a title to the admonition by adding it after the type:
```markdown
:::warning{title="Warning of the day"}
The path of the righteous man is beset on all sides by the iniquities of the selfish and the tyranny
of evil men.
:::
```
:::warning{title="Warning of the day"}
The path of the righteous man is beset on all sides by the iniquities of the selfish and the tyranny
of evil men.
:::
## Usage with Prettier
If you use Prettier to format your Markdown files, Prettier might auto-format your code to invalid
admonition syntax. To avoid this problem, add empty lines around the starting and ending directives.
This is also why the examples we show here all have empty lines around the content.
```markdown
:::note
Hello world
:::
:::note
Hello world
:::
::: note Hello world:::
```
---
## Document: Multiple APIs
URL: /docs/guides/using-multiple-apis
# Multiple APIs
Zudoku supports creating documentation and API references for multiple APIs and can work with as
many OpenAPI documents as you need.
In order to do this you will need to modify the [Zudoku Configuration](../configuration/overview.md)
file to include additional APIs.
## Configuration
Using multiple APIs is a configuration setting that you can add in the
[Zudoku Configuration](../configuration/overview.md) file.
### Step 1: Add your APIs
First, create a new array in your configuration file that lists each API you want to include:
```typescript
const apis = [
{
type: "file",
input: "apis/my-first-api.json",
path: "/my-first-api",
},
{
type: "file",
input: "apis/my-second-api.json",
path: "/my-second-api",
},
] as const;
```
### Step 2: Add navigation
Create a navigation array for your sidebar:
```typescript
const navigation = [
{
type: "link",
label: "My First API",
to: "/my-first-api",
},
{
type: "link",
label: "My Second API",
to: "/my-second-api",
},
] as const;
```
### Step 3: Update your config
Modify your [Zudoku Configuration](../configuration/overview.md) file to include these arrays:
```typescript
import type { ZudokuConfig } from "zudoku";
const config: ZudokuConfig = {
navigation: [
{
type: "category",
label: "Overview",
items: navigation,
},
],
redirects: [{ from: "/", to: "/overview" }],
apis,
docs: {
files: "/pages/**/*.{md,mdx}",
},
};
export default config;
```
Make sure that:
1. The `path` in each API config matches the `to` in the navigation
2. Your OpenAPI files are placed in the correct location as specified in the `input` field
3. The `label` in navigation matches what you want to display in the sidebar
You don't necessarily need to add the APIs to your sidebar, you can also put them into the top
navigation or link to them from your docs.
---
## Document: Transforming Operation Examples
URL: /docs/guides/transforming-examples
# Transforming Operation Examples
Zudoku allows you to transform operation examples in both request and response sections of your API
documentation. This feature is particularly useful when you need to:
- Modify example data before displaying it
- Add dynamic values to examples
- Format examples in a specific way
- Filter or transform example content based on certain conditions
## Configuration
To use this feature, you need to configure the `transformExamples` function in your
`zudoku.config.tsx` file. Here's how to do it:
```tsx
import type { ZudokuConfig } from "zudoku";
const config: ZudokuConfig = {
// ... other config options ...
defaults: {
apis: {
transformExamples: (options) => {
// Transform the content here
return options.content;
},
},
},
};
```
## The Transform Function
The `transformExamples` function receives an options object with the following properties:
1. `content`: An array of Content objects containing the example data
1. `operation`: The operation being displayed
1. `type`: Either "request" or "response" indicating which type of example is being transformed
1. `auth`: The current authentication state
1. `context`: ZudokuContext
The function should return an array of Content objects with the transformed examples.
## Example Usage
Here's a practical example showing how to transform examples:
```tsx
const config: ZudokuConfig = {
defaults: {
apis: {
transformExamples: ({ content, type }) => {
// Example: Add a timestamp to all examples
const timestamp = new Date().toISOString();
return content.map((contentItem) => ({
...contentItem,
example: {
...contentItem.example,
timestamp,
// You can modify other example properties here
},
}));
},
},
},
};
```
## Use Cases
### Adding Dynamic Values
```tsx
transformExamples: ({ content, auth }) => {
const apiKey = auth.accessToken;
return content.map((contentItem) => ({
...contentItem,
example: {
...contentItem.example,
headers: {
...contentItem.example.headers,
Authorization: `Bearer ${apiKey}`,
},
},
}));
};
```
### Formatting Examples
```tsx
transformExamples: ({ content }) => {
return content.map((contentItem) => ({
...contentItem,
example: {
...contentItem.example,
// Format dates in a specific way
createdAt: new Date(contentItem.example.createdAt).toLocaleDateString(),
// Format numbers with specific precision
amount: Number(contentItem.example.amount).toFixed(2),
},
}));
};
```
### Conditional Transformation
```tsx
transformExamples: ({ content, auth, type }) => {
const isAuthenticated = auth.isAuthenticated;
return content.map((contentItem) => ({
...contentItem,
example: isAuthenticated
? contentItem.example // Show full example for authenticated users
: { ...contentItem.example, sensitiveData: undefined }, // Hide sensitive data for unauthenticated users
}));
};
```
### Using JWT Claims
```tsx
transformExamples: async ({ content, auth, context }) => {
const token = await context.authentication.getAccessToken();
// Decode the JWT (this is a simple example - in production you might want to use a proper JWT library)
const [, payload] = token.split(".");
const decodedPayload = JSON.parse(atob(payload));
return content.map((contentItem) => ({
...contentItem,
example: {
...contentItem.example,
// Add user-specific data from the JWT
userId: decodedPayload.sub,
organizationId: decodedPayload.org_id,
// You can add any other claims from the JWT
role: decodedPayload.role,
},
}));
};
```
## Best Practices
1. Always return an array of Content objects, even if you're not transforming the content
2. Preserve the original content structure while making your modifications
3. Handle errors gracefully to prevent breaking the documentation
4. Consider performance implications when transforming large examples
5. Use the provided options object to access relevant information for your transformations
---
## Document: Static Files
Learn how to serve and reference static files like images and PDFs in your Zudoku documentation using the public directory.
URL: /docs/guides/static-files
# Static Files
Zudoku makes it easy to serve static files like images, PDFs, or any other assets alongside your
documentation. Any files placed in the `public` directory will be served at the root path `/` during
dev, and copied to the root of the dist directory as-is.
Note that you should always reference `public` assets using root absolute path - for example,
`public/icon.png` should be referenced in source code as `/icon.png`.
## Usage
1. Create a `public` directory in your project root if it doesn't exist already
2. Place any static files in this directory
3. Reference these files in your documentation using the root path `/`
## Example
If you have the following structure:
```
your-project/
├── public/
│ ├── images/
│ │ └── diagram.png
│ └── documents/
│ └── api-spec.pdf
└── ...
```
You can reference these files using markdown like this:
```md

```
If you want users to download a file like a PDF, you can use an anchor tag like this:
```html
Download API specification
```
## Relative paths
If you want to reference a file that is in the same directory as the current file, you can also use
a relative path:
```md title="page.mdx"

```
---
## Document: Server-side Content Protection
How Zudoku isolates protected-route content at build time in SSR mode. Covers the auto-detection rules, caveats for dynamic routes and inline content, and a pre-ship checklist.
URL: /docs/guides/server-side-content-protection
# Server-side Content Protection
When you run Zudoku in SSR mode, [`protectedRoutes`](../configuration/protected-routes.md) is
enforced beyond the runtime login dialog. The JavaScript chunks containing content for protected
routes are physically separated from the public bundle and served only through an auth-gated
endpoint. Unauthenticated users cannot fetch them even if they know the URL.
## Why this exists
In a typical SPA build, every page's JavaScript is code-split into a chunk in `/assets/`. Any
browser can fetch any chunk URL. A runtime `RouteGuard` can block _rendering_ a protected page, but
the code itself is still downloadable.
In SSR mode, the build additionally:
1. Classifies each code-split chunk as public or protected based on which routes it serves.
2. Moves protected chunks from the public output into the server bundle, so they're no longer served
as plain static files.
3. Registers an auth-gated route at `/_protected/*` on the SSR adapter that requires a valid session
cookie.
A request to a protected chunk URL without a session returns `401 Unauthorized`. Combined with
`RouteGuard` on render, protected content stays on the server.
## How classification works
At build time, a Vite transform AST-scans your code for route-shaped dynamic imports and records
`{moduleId → subtree root}` entries in a registry. Two shapes are auto-detected.
### Shape A: object literal with `path`
Any object literal with a string `path` property. Every dynamic `import()` inside the object's other
property values is registered as subtree-scoped at that path.
```ts
// Standard React Router route
{ path: "/admin", lazy: () => import("./AdminPage") }
// Also matches plugin-api's generated code
openApiPlugin({
path: "/my-api",
schemaImports: {
"...processed/file.js": () => import("...processed/file.js?d=..."),
},
});
```
### Shape B: dict keyed by route path
An object whose keys are route-path strings (start with `/`, contain no `.`) mapping to arrow
functions that call `import()`.
```ts
const fileImports = {
"/docs/intro": () => import("./intro.mdx"),
"/docs/guides": () => import("./guides.mdx"),
};
```
The dot guard keeps file-path dicts (like `{"/abs/path/x.js": ...}`) from being mistaken for route
dicts.
### From registry to chunking
1. The annotator transform scans every first-party module and populates the registry.
2. Rolldown's `manualChunks` callback consults the registry for each module. If any registered
subtree for that module intersects a `protectedRoutes` pattern, the module goes into a
`protected-*` chunk.
3. After bundling, protected chunks are renamed into a `_protected/` directory and moved from the
client output to the server output.
4. A static-reachability assertion fails the build if any public chunk statically imports a
protected chunk (which would eagerly pull gated code into the public bundle).
## What's covered out of the box
| Content source | Shape | Auto-detected? |
| -------------------------------- | ----------------------------- | -------------- |
| MDX docs (`plugin-docs`) | Shape B (route dict) | ✅ |
| File OpenAPI (`plugin-api`) | Shape A (via `openApiPlugin`) | ✅ |
| User custom pages with `lazy` | Shape A (`{path, lazy}`) | ✅ |
| User custom pages with `element` | Not code-split | ❌ (see below) |
| URL-based OpenAPI (`type: url`) | Fetched at runtime | ❌ (see below) |
| Raw inline OpenAPI (`type: raw`) | Inlined in main bundle | ❌ (see below) |
## Caveats
### Dynamic route paths
The annotator only recognizes string literals. Configs that generate routes with computed paths are
not detected:
```ts
// Not detected: path and specifier are template literals.
navigation: items.map((i) => ({
type: "custom-page",
path: `/foo/${i.slug}`,
lazy: () => import(`./Foo-${i.slug}`),
}));
```
**Fix:** nest the dynamic entries under a static-path ancestor so the outer Shape A match catches
them:
```ts
{
type: "category",
path: "/foo",
items: items.map((i) => ({
type: "custom-page",
path: i.slug,
lazy: () => import(`./Foo-${i.slug}`),
})),
}
```
The outer `{path: "/foo", ...}` registers every nested dynamic import as subtree-scoped at `/foo`,
so `protectedRoutes: ["/foo/*"]` covers them all. Alternatively, write the entries out with literal
paths.
### Inline JSX custom pages
Writing
```ts
{ type: "custom-page", path: "/secret", element: }
```
ships `` directly in the main bundle. There's no chunk to gate and no URL to block; the
runtime `RouteGuard` prevents rendering but the JavaScript is already on the user's machine.
**Fix:** switch to `lazy`:
```ts
{ type: "custom-page", path: "/secret", lazy: () => import("./Secret") }
```
### URL-based OpenAPI specs
`{ type: "url", input: "https://example.com/api.yaml" }` fetches at runtime from whatever origin you
configure. Auth is your responsibility on that origin. Zudoku cannot gate a URL it does not serve.
### Raw inline OpenAPI specs
`{ type: "raw", input: {...} }` embeds the spec as a JS object literal in the bundle. Same situation
as inline custom pages: no chunk, no way to gate at the bundle level.
### Third-party and custom plugins
If a plugin emits code-split routes in neither Shape A nor Shape B, its chunks aren't detected. Two
options:
1. Have the plugin emit a detectable shape. Usually the easiest: wrap the generated routes in an
object with a string `path`.
2. Register directly. Plugins can call
`registerProtectedScope(moduleId, {type: "subtree", root: "/your-path"})` from their Vite `load`
hook.
## The build-time check
If a `protectedRoutes` pattern has no registered content, the build fails:
```
[zudoku] protectedRoutes patterns with no matching content: "/admin/*".
Either the pattern is a typo, or the route uses an inline element / dynamic path
that isn't code-split. Load the route via dynamic import so it gets its own chunk,
otherwise its JS ships in the public bundle.
```
Three things to check:
1. **Typo.** Does the pattern match any real route?
2. **Dynamic content.** Computed paths? Apply the nested-subtree fix above.
3. **Inline content.** Is the route served by an inline JSX element or a raw spec? It cannot be
gated at the bundle level; move the content into a code-split module.
If none of those apply and you're sure the content should be detected, file an issue with a minimal
reproduction.
## Dev mode and SSG
**Dev mode** doesn't chunk-split the same way as production, so the bundle-level gating is absent.
Only the runtime `RouteGuard` applies. Use a production SSR build to verify gating.
**SSG builds** have no server. `protectedRoutes` in SSG falls back to client-side enforcement only:
`RouteGuard` blocks rendering, but chunks remain publicly fetchable. If content must stay
server-side, use an SSR adapter.
## Pre-ship checklist
- [ ] Build passes (any unmatched `protectedRoutes` pattern fails the build).
- [ ] Any custom pages meant to be protected use `lazy: () => import(...)`, not `element`.
- [ ] Any dynamically-generated protected routes are nested under a static-path ancestor.
- [ ] URL-based and raw inline OpenAPI specs have their own access control at their origin.
- [ ] Visit a protected chunk URL directly in an unauthenticated browser (grab one from DevTools)
and confirm you get `401 Unauthorized`.
## Related
- [Protected Routes](../configuration/protected-routes.md): the `protectedRoutes` config API.
- [Authentication](../configuration/authentication.md): wiring up an auth provider so sessions
exist.
---
## Document: Redirects
Configure URL redirects in your Zudoku developer portal to set landing pages, maintain backward compatibility, and handle URL changes. Covers redirect behavior, basePath interaction, and troubleshooting.
URL: /docs/guides/redirects
# Redirects
Redirects let you automatically send visitors from one URL to another in your developer portal. They
are useful when you need to:
- Set a custom landing page for the root path (`/`)
- Maintain backward compatibility after restructuring documentation
- Point old API endpoint paths to their new locations
- Redirect from deprecated pages to their replacements
## Basic configuration
Add a `redirects` array to your `zudoku.config.ts` file. Each entry has a `from` path and a `to`
path:
```ts title="zudoku.config.ts"
import type { ZudokuConfig } from "zudoku";
const config: ZudokuConfig = {
// ... other config
redirects: [
{ from: "/", to: "/introduction" },
{ from: "/getting-started", to: "/quickstart" },
],
};
export default config;
```
When a visitor navigates to the `from` path, they are automatically redirected to the `to` path.
## Redirect properties
Each redirect object accepts two properties:
- **`from`** — The path you want to redirect away from. An absolute path starting with `/` is
recommended. If you omit the leading slash, it is normalized automatically.
- **`to`** — The destination path where visitors will be sent. This can be any valid path in your
portal.
```ts
redirects: [{ from: "/old-page", to: "/new-page" }];
```
Trailing slashes in the `from` path are normalized automatically. Both `/old-page` and `/old-page/`
will match the same redirect rule.
## How redirects work
Zudoku redirects operate at two levels depending on how the visitor reaches the page:
### Server-side behavior
When a visitor loads a redirect path directly (for example, by typing the URL in the browser address
bar or following an external link), the exact response depends on your deployment target:
- **Zuplo** and **Vercel** (or any platform that reads the Vercel Build Output API): the platform
returns an HTTP **301 (Moved Permanently)** with a `Location` header, so browsers and search
engines see a true permanent redirect.
- **SSR deployments**: the server returns a real HTTP 301 from the router loader.
- **Other static hosts** (Netlify, Cloudflare Pages, GitHub Pages, S3, nginx, etc.): Zudoku
prerenders each redirect source path as a small HTML file containing a JavaScript redirect. The
file is served with a 200 response and the browser follows the redirect once the script runs.
JavaScript must be enabled for the redirect to fire.
### Client-side behavior
When a visitor clicks an internal link that points to a redirect path, Zudoku handles the redirect
entirely in the browser using client-side routing. The visitor is navigated to the destination
without a full page reload, providing a seamless experience.
Both behaviors land the visitor on the destination page. For search engines, only the 301 variants
signal a permanent move; the JavaScript redirect used on generic static hosts is weaker for SEO.
## Common patterns
### Setting a landing page
The most common use of redirects is setting a landing page for the root path of your portal:
```ts
redirects: [{ from: "/", to: "/docs/introduction" }];
```
This sends visitors who arrive at your portal's root URL to your introduction page.
### Reorganizing documentation
When you restructure your documentation, add redirects from the old paths so existing bookmarks and
external links continue to work:
```ts
redirects: [
{ from: "/api/authentication", to: "/guides/auth-overview" },
{ from: "/api/getting-started", to: "/docs/quickstart" },
{ from: "/reference", to: "/api" },
];
```
### Redirecting to specific sections
You can redirect to a specific section of a page using a hash fragment in the `to` path:
```ts
redirects: [
{
from: "/api-shipments/create-shipment",
to: "/api-shipments/shipment-management#post-shipments",
},
{
from: "/api-shipments/get-rates",
to: "/api-shipments/rates-and-billing#post-shipments-shipmentid-rates",
},
];
```
This is useful when multiple old pages have been consolidated into a single page with distinct
sections.
### Redirecting category paths
If your API reference groups endpoints into categories, you may want the category path to redirect
to a specific endpoint or overview page:
```ts
redirects: [
{ from: "/api/billing", to: "/api/billing/get-invoices" },
{ from: "/api/users", to: "/api/users/list-users" },
];
```
## Redirects and the `basePath` option
If your portal uses a [`basePath`](/docs/configuration/overview#basepath), redirects are defined
_relative to the base path_. Zudoku automatically prepends the base path to both the matched `from`
and the emitted `Location`.
For example, with `basePath: "/docs"`:
```ts
redirects: [{ from: "/", to: "/getting-started" }];
// Visitors to /docs/ are redirected to /docs/getting-started
```
You do not need to include the base path in your `from` or `to` values.
## Redirects vs. navigation rules
Zudoku offers two features that can change where visitors end up: [redirects](#basic-configuration)
and [navigation rules](/docs/guides/navigation-rules). They serve different purposes:
- **Redirects** map one URL to another. Use them when a page has moved or when you need a specific
URL to point somewhere else. They affect both direct visits and internal link clicks.
- **Navigation rules** customize the _sidebar_ generated by plugins like the OpenAPI plugin. Use
them to insert, reorder, modify, or remove items in the sidebar without changing the underlying
page URLs.
If you want to change where a URL takes visitors, use a redirect. If you want to change what appears
in the sidebar navigation, use a navigation rule.
## Redirects and sitemaps
Redirect source paths are automatically excluded from your
[sitemap](/docs/configuration/overview#sitemap). Only the destination pages appear in the generated
`sitemap.xml`, which prevents search engines from indexing the old URLs.
## Troubleshooting
### Redirect returns a 404
Make sure the `to` path points to a page that actually exists in your portal. If the destination is
an API reference page generated from an OpenAPI spec, verify that the path matches the generated
route. You can check available routes by running your dev server and navigating manually.
### Redirect conflicts with another route
Redirects are registered before page routes, so when a redirect `from` path exactly matches another
route, the redirect wins. If you want a page to be reachable at a given path, remove the conflicting
redirect rather than relying on route priority.
---
## Document: /docs/guides/processors
URL: /docs/guides/processors
# Schema Processors
Schema processors are functions that transform your OpenAPI schemas before they are used in the
documentation. They are defined in your `zudoku.build.ts` file and can be used to modify schemas in
various ways.
:::tip
For information on how to configure processors in your project, see the
[Build Configuration](../configuration/build-configuration) guide.
:::
## Built-in Processors
Zudoku provides several built-in processors that you can use to transform your schemas:
### `removeExtensions`
Removes OpenAPI extensions (`x-` prefixed properties) from your schema:
```ts
import { removeExtensions } from "zudoku/processors/removeExtensions";
import type { ZudokuBuildConfig } from "zudoku";
const buildConfig: ZudokuBuildConfig = {
processors: [
// Remove all x- prefixed properties
removeExtensions(),
// Remove specific extensions
removeExtensions({
keys: ["x-internal", "x-deprecated"],
}),
// Remove extensions based on a custom filter
removeExtensions({
shouldRemove: (key) => key.startsWith("x-zuplo"),
}),
],
};
export default buildConfig;
```
### `removeParameters`
Removes parameters from your schema:
```ts
import { removeParameters } from "zudoku/processors/removeParameters";
import type { ZudokuBuildConfig } from "zudoku";
const buildConfig: ZudokuBuildConfig = {
processors: [
// Remove parameters by name
removeParameters({
names: ["apiKey", "secret"],
}),
// Remove parameters by location
removeParameters({
in: ["header", "query"],
}),
// Remove parameters based on a custom filter
removeParameters({
shouldRemove: ({ parameter }) => parameter["x-internal"],
}),
],
};
export default buildConfig;
```
### `removePaths`
Removes paths or operations from your schema:
```ts
import { removePaths } from "zudoku/processors/removePaths";
import type { ZudokuBuildConfig } from "zudoku";
const buildConfig: ZudokuBuildConfig = {
processors: [
// Remove entire paths
removePaths({
paths: {
"/internal": true,
"/admin": ["get", "post"],
},
}),
// Remove paths based on a custom filter
removePaths({
shouldRemove: ({ path, method, operation }) => operation["x-internal"],
}),
],
};
export default buildConfig;
```
:::info
If you are missing a processor that you think should be built-in, please don't hesitate to
[open an issue on GitHub](https://github.com/zuplo/zudoku/issues/new).
:::
## Custom Processors
You can also create your own processors. Here's a simple example that adds a description to all
operations:
```ts
import type { ZudokuBuildConfig } from "zudoku";
async function addDescriptionProcessor({ schema }) {
if (!schema.paths) return schema;
// Add a description to all operations
Object.values(schema.paths).forEach((path) => {
Object.values(path).forEach((operation) => {
if (typeof operation === "object") {
operation.description = "This is a public API endpoint";
}
});
});
return schema;
}
const buildConfig: ZudokuBuildConfig = {
processors: [addDescriptionProcessor],
};
export default buildConfig;
```
### Adding Server URLs
```ts
import type { ZudokuBuildConfig } from "zudoku";
async function addServerUrl({ schema }) {
return {
...schema,
servers: [{ url: "https://api.example.com" }],
};
}
const buildConfig: ZudokuBuildConfig = {
processors: [addServerUrl],
};
export default buildConfig;
```
## Using Query Parameters to Split Schemas
You can use the same OpenAPI file multiple times with different processing by appending query
parameters to the file input string. These parameters are passed to your processors via the `params`
argument, allowing you to filter or transform the schema differently for each variant.
This is useful when you have a single schema containing multiple API versions or groups that you
want to split into separate pages.
Plain strings work out of the box. Version paths and dropdown labels are auto-generated from the
query parameter values. For more control, use the object form with explicit `path` and `label`.
```ts title=zudoku.config.ts
const config = {
apis: {
type: "file",
input: [
// Object form: explicit path and label
{
input: "openapi.json?prefix=/v2",
path: "latest",
label: "Latest (v2)",
},
// Object form: explicit path, label auto-generated from params
{
input: "openapi.json?prefix=/v1.1",
path: "v1.1",
},
// Plain string: both path and label auto-generated from params
"openapi.json?prefix=/v1",
],
path: "/api",
},
};
```
```ts title=zudoku.build.ts
import type { ZudokuBuildConfig } from "zudoku";
const buildConfig: ZudokuBuildConfig = {
processors: [
({ schema, params }) => {
const prefix = params.prefix;
if (!prefix) return schema;
return {
...schema,
paths: Object.fromEntries(
Object.entries(schema.paths ?? {}).filter(([path]) => path.startsWith(prefix)),
),
};
},
],
};
export default buildConfig;
```
Each version tab will show only the endpoints matching that prefix. The query parameters are
arbitrary key-value pairs that you define and consume in your processors.
When schema download is enabled, param variants serve the processed (filtered) schema rather than
the original file.
## Best Practices
- **Handle missing properties**: Check for the existence of properties before accessing them
- **Return the schema**: Always return the transformed schema, even if no changes were made
- **Use async/await**: Processors can be async functions, which is useful for more complex
transformations
- **Chain processors**: Processors are executed in order, so you can chain multiple transformations
---
## Document: Navigation Rules
URL: /docs/guides/navigation-rules
# Navigation Rules
import { LayersPlusIcon, PencilIcon, SortAscIcon, ArrowUpDownIcon, TrashIcon } from "zudoku/icons";
Plugins like the OpenAPI plugin generate sidebar navigation automatically. Navigation rules let you
customize that generated sidebar without changing the source. You can:
- **Insert** items at a specific position
- **Modify** items
- **Sort** items with a custom comparator
- **Move** items to a different position
- **Remove** items
## Setup
Add a `navigationRules` array to your `zudoku.config.tsx`:
```tsx
import type { ZudokuConfig } from "zudoku";
const config: ZudokuConfig = {
apis: [{ type: "file", input: "./api.json", path: "api-shipments" }],
navigation: [{ type: "link", label: "Shipments", to: "/api-shipments" }],
navigationRules: [
// rules go here
],
};
```
## Inserting docs
Use `type: "insert"` to add items before or after a matched sidebar item. The `match` string uses
the tab label as the first segment and navigates into the sidebar tree.
```tsx
navigationRules: [
{
type: "insert",
match: "Shipments/0",
position: "before",
items: [
{
type: "doc",
file: "api-shipments/getting-started",
icon: "plane-takeoff",
},
],
},
],
```
This inserts a "Getting Started" doc before the first item in the Shipments sidebar.

The MDX file lives at `pages/api-shipments/getting-started.mdx` (under your configured docs
directory, matching the API's base path).
## Adding links
You can insert external or internal links the same way:
```tsx
{
type: "insert",
match: "Shipments/-1",
position: "after",
items: [
{
type: "link",
label: "System Status",
to: "/status",
icon: "satellite",
},
],
}
```
The `-1` index targets the last item, and `position: "after"` places the link at the very end of the
sidebar.

## Modifying items
Use `type: "modify"` to change properties of existing sidebar items like their icon, label, or
collapsed state:
```tsx
{
type: "modify",
match: "Shipments/Shipment Management",
set: { icon: "box", collapsed: true },
}
```

## Removing items
`type: "remove"` hides items from the sidebar. This is rarely needed since it's usually better to
fix the underlying source, but can be useful as a quick workaround:
```tsx
{
type: "remove",
match: "Shipments/Deprecated Endpoint",
}
```
## Sorting items
Use `type: "sort"` to reorder children of a category alphabetically or with any custom comparator:
```tsx
{
type: "sort",
match: "Shipments",
by: (a, b) => a.label.localeCompare(b.label),
}
```
The `by` function receives two navigation items and works like a standard `Array.sort` comparator.
## Moving items
Use `type: "move"` to relocate an existing item to a different position in the sidebar:
```tsx
{
type: "move",
match: "Shipments/Track a Shipment",
to: "Shipments/0",
position: "before",
}
```
This moves "Track a Shipment" to the top of the Shipments category. You can also move items between
different levels, for example from inside a category to the root level.
## Rule order
Rules are applied sequentially, so order matters. For example, sorting first and then inserting
places the new item at an exact position in the already-sorted list. Inserting first and then
sorting will sort the new item along with everything else.
## Complete example
This is the configuration used in the Cosmo Cargo demo shown above:
```tsx
navigationRules: [
{
type: "sort",
match: "Shipments",
by: (a, b) => a.label.localeCompare(b.label),
},
{
type: "insert",
match: "Shipments/Shipment",
position: "before",
items: [
{
type: "doc",
file: "api-shipments/getting-started",
icon: "plane-takeoff",
},
],
},
{
type: "insert",
match: "Shipments/-1",
position: "after",
items: [
{
type: "link",
label: "System Status",
to: "/status",
icon: "satellite",
},
],
},
{
type: "modify",
match: "Shipments/1",
set: { icon: "box" },
},
],
```
## Match syntax reference
For the full match syntax including label matching, index selectors, and nested paths, see the
[Navigation Rules reference](/docs/configuration/navigation#navigation-rules).
---
## Document: Navigation Migration
URL: /docs/guides/navigation-migration
# Navigation Migration
This guide explains how to migrate existing configurations that used `topNavigation`, `sidebar` and
`customPages` to the new unified `navigation` configuration introduced in vNEXT.
## Overview
Navigation is now configured through a single `navigation` array. Items at the root level become top
navigation tabs, while nested categories automatically form the sidebar. Custom pages are added
using the `custom-page` item type.
## Before and After
```tsx title="Before"
const config: ZudokuConfig = {
topNavigation: [
{ id: "docs", label: "Docs" },
{ id: "api", label: "API" },
],
sidebar: {
docs: [{ type: "doc", id: "introduction" }],
},
customPages: [{ path: "/playground", render: Playground, prose: false }],
apis: {
type: "file",
input: "./openapi.json",
navigationId: "api",
},
};
```
```tsx title="After"
const config: ZudokuConfig = {
navigation: [
{
type: "category",
label: "Docs",
items: ["introduction"],
},
{
type: "custom-page",
path: "/playground",
element: ,
},
{
type: "link",
to: "/api",
label: "API",
},
],
apis: [
{
path: "/api",
type: "file",
input: "./openapi.json",
},
],
};
```
## Migration steps
1. **Create a `navigation` array**
Move all items from `topNavigation` and your sidebar into a new `navigation` array.
1. **Convert custom pages**
Replace entries in `customPages` with `type: "custom-page"` items inside `navigation`.
1. **Update plugin configs**
Replace all uses of `navigationId` with `path` in plugin options like `apis` or `catalogs`.
Navigation items of type `link` should use the `to` property to reference the path of the API or
catalog.
1. **Reference plugin paths in navigation**
Items produced by plugins are not added automatically. Add links or categories in your
`navigation` so users can access them.
---
## Document: Mermaid Diagrams
URL: /docs/guides/mermaid
# Mermaid Diagrams
Zudoku supports rendering [Mermaid diagrams](https://mermaid.js.org/) in two ways:
| Approach | Pros | Cons |
| ------------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------- |
| **Build-Time** (rehype-mermaid) | • Faster page loads • No client-side JS needed • SEO friendly | • Requires playwright • Slower builds • Static only |
| **Client-Side** (``) | • Fast builds • Can be dynamic • No build dependencies | • Requires client-side JS • Slight render delay |
## Client-Side Rendering
For the [`` component](/docs/components/mermaid), install the peer dependency:
```bash
npm install mermaid
```
Then use in your MDX files (no import needed):
```tsx
B;
A-->C;`}
/>
```
Outside of MDX, import from `zudoku/mermaid`:
```tsx
import { Mermaid } from "zudoku/mermaid";
```
See the [Mermaid component documentation](/docs/components/mermaid) for full details.
## Build-Time Rendering
1. Install dependencies:
```bash
npm install rehype-mermaid
npm install -D playwright
npx playwright install
```
1. Add to `zudoku.build.ts`:
```tsx title="zudoku.build.ts"
import rehypeMermaid from "rehype-mermaid";
export default {
rehypePlugins: (plugins) => [[rehypeMermaid, { strategy: "inline-svg" }], ...plugins],
};
```
1. Use in markdown with code blocks:
````mdx
```mermaid
graph TD;
A-->B;
```
````
---
## Document: Documenting MCP Servers
URL: /docs/guides/mcp-servers
# Documenting MCP Servers
Zudoku can render a dedicated [MCP](https://modelcontextprotocol.io/) endpoint UI for any OpenAPI
operation that has the `x-mcp-server` extension. When detected, the operation page replaces the
standard request/response view with an MCP card showing the endpoint URL, a copy button, and tabbed
installation instructions for Claude, ChatGPT, Cursor, VS Code, and a generic config.
## Adding the extension
Add the `x-mcp-server` extension to an operation in your OpenAPI spec. While MCP servers typically
use `POST`, the extension works on any HTTP method:
```json title="openapi.json (paths section)"
{
"paths": {
"/mcp": {
"post": {
"summary": "My MCP Server",
"description": "MCP endpoint for querying documentation.",
"operationId": "mcpEndpoint",
"x-mcp-server": {
"name": "my-mcp-server",
"version": "1.0.0",
"tools": [
{
"name": "search_docs",
"description": "Search the documentation"
},
{
"name": "get_page",
"description": "Retrieve a specific documentation page"
}
]
},
"responses": {
"200": {
"description": "MCP response"
}
}
}
}
}
}
```
The UI will display beneath the operation heading, showing the full MCP URL derived from the server
URL and the operation path.
You can also use the shorthand `"x-mcp-server": true` to enable the MCP UI without specifying any
metadata. In this case, the operation's `summary` is used as the server name.
## Extension properties
| Property | Type | Required | Description |
| --------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `name` | `string` | No | Display name used in the generated client configuration snippets. Falls back to the operation `summary`, then `"mcp-server"` |
| `version` | `string` | No | Version metadata (included for completeness; not currently rendered in UI) |
| `tools` | `array` | No | Tools metadata (used by Zuplo enrichment; not currently rendered in UI) |
Each tool in the `tools` array has:
| Property | Type | Required | Description |
| ------------- | -------- | -------- | ------------------------------- |
| `name` | `string` | Yes | Tool name |
| `description` | `string` | No | Human-readable tool description |
## MCP URL resolution
The displayed MCP URL is constructed from the **server URL** of the API and the **path** of the
operation. The server URL comes from the OpenAPI `servers` array (or the operation-level `servers`
override if present).
For example, with this configuration:
```json
{
"servers": [{ "url": "https://api.example.com" }],
"paths": {
"/mcp/docs": {
"post": {
"x-mcp-server": { "name": "docs-mcp" },
"responses": { "200": { "description": "OK" } }
}
}
}
}
```
The displayed MCP URL will be `https://api.example.com/mcp/docs`.
## Complete example
This is a minimal but complete OpenAPI spec that produces an MCP endpoint page:
```json title="mcp-api.json"
{
"openapi": "3.0.3",
"info": {
"title": "Documentation MCP Server",
"version": "1.0.0"
},
"servers": [
{
"url": "https://api.example.com",
"description": "Production"
}
],
"paths": {
"/mcp": {
"post": {
"tags": ["MCP"],
"summary": "Documentation MCP Server",
"description": "MCP endpoint powered by Inkeep for searching and querying documentation.",
"operationId": "mcpEndpoint",
"x-mcp-server": {
"name": "example-docs",
"version": "1.0.0",
"tools": [
{
"name": "search_docs",
"description": "Search the documentation"
}
]
},
"responses": {
"200": {
"description": "MCP response"
}
}
}
}
}
}
```
Then reference this spec in your Zudoku config (see
[API Reference](/docs/configuration/api-reference) for full `apis` configuration):
```tsx title="zudoku.config.tsx"
import type { ZudokuConfig } from "zudoku";
const config: ZudokuConfig = {
apis: [
{
type: "file",
input: "./mcp-api.json",
path: "mcp",
},
],
navigation: [
{
type: "link",
label: "MCP Server",
to: "/mcp",
icon: "bot",
},
],
};
export default config;
```
You can see a live example of this in the
[Cosmo Cargo demo](https://www.cosmocargo.dev/catalog/api-ai-cargo/ai-operations#universal-mcp-endpoint).
## Generated UI
When Zudoku detects the `x-mcp-server` extension on an operation, the page shows:
- **MCP Endpoint card** with the full URL and a copy button
- **AI Tool Configuration** tabs with setup instructions for:
- **Claude** — add via Connectors UI or `claude mcp add` CLI command
- **ChatGPT** — app setup via Settings → Apps → Advanced Settings
- **Cursor** — `mcp.json` configuration (global or project-level)
- **VS Code** — `.vscode/mcp.json` with native HTTP transport for GitHub Copilot
- **Generic** — standard `mcp.json` format compatible with most MCP clients
The standard method badge, request body, parameters, and sidecar panels are hidden for MCP endpoints
since they use a different interaction model.
## Using with Zuplo
If you are using [Zuplo](https://zuplo.com) to host your API, the `x-mcp-server` extension is
automatically added to POST operations that use the `mcpServerHandler`. No manual schema changes are
needed. See the [Zuplo MCP documentation](https://zuplo.com/docs/handlers/mcp-server) for details.
---
## Document: Managing API Keys and Identities
URL: /docs/guides/managing-api-keys-and-identities
# Managing API Keys and Identities
## Managing API Keys in UI
Zudoku comes with a Plugin to manage API keys in the UI. This plugin is build around the concept of
API consumers, where each consumer can have multiple API keys. It includes functionality for
creating, viewing, updating, and deleting API keys, as well as managing consumer information.
:::note
The most convenient way to use this plugin is to use it in Zuplo. However the plugin can be used
with any other API key management system - you can use a 3rd party API key management system or
build your own and still use the Zudoku UI.
:::
## Using in Zuplo
To get started with a basic setup, simply enable `apiKeys` in your config. (In the default template,
this is already enabled.)
```typescript title=zudoku.config.ts
{
// ...
apiKeys: {
enabled: true,
}
}
```
This will make the API Key UI available at `/settings/api-keys` in your Zudoku.
### Creating API Keys
By default, there is no **Create API Key** Button. To get the button working you have to implement
the `createKey` callback in your config. This callback is called when the user clicks the **Create
API Key** button. The callback is passed the `apiKey` object that the user has entered, the
`context` object, and the `auth` object. The `apiKey` object is the object that the user has entered
in the Create API Key form.
In this callback you can implement the logic to create a new API key. You can use the `auth` object
to access the current users auth state. Most likley you want to call your API to create a new API
key.
```typescript title=zudoku.config.ts
const config = {
// ...
apiKeys: {
enabled: true,
createKey: async ({ apiKey, context, auth }) => {
const createApiKeyRequest = new Request("https://api.example.com/v1/developer/api-key", {
method: "POST",
body: JSON.stringify({
apiKey,
}),
});
// Sign the request using current users Authentication
const signedRequest = await context.signRequest(createApiKeyRequest);
const response = await fetch(signedRequest);
if (!response.ok) {
throw new Error("Could not create API Key");
}
return true;
},
},
};
```
## Using with any API Key Management System
If you are not using Zuplo's API key management system, you can implement the `ApiKeyService`
interface to connect to any API key management system.
```typescript
interface ApiKeyService {
getConsumers: (context: ZudokuContext) => Promise;
rollKey?: (consumerId: string, context: ZudokuContext) => Promise;
deleteKey?: (consumerId: string, keyId: string, context: ZudokuContext) => Promise;
updateConsumer?: (
consumer: { id: string; label?: string },
context: ZudokuContext,
) => Promise;
getUsage?: (apiKeys: string[], context: ZudokuContext) => Promise;
createKey?: ({
apiKey,
context,
auth,
}: {
apiKey: { description: string; expiresOn?: string };
context: ZudokuContext;
auth: UseAuthReturn;
}) => Promise;
}
interface ApiConsumer {
id: string;
label: string;
keys: ApiKey[];
}
interface ApiKey {
id: string;
key: string;
description?: string;
createdOn: string;
expiresOn?: string;
}
```
### Implementing API Key Management
Here's a step-by-step guide to implementing API key management:
1. First, create a service that implements the `ApiKeyService` interface:
```typescript title=zudoku.config.ts
const config = {
// ...
apiKeys: {
enabled: true,
// Retrieve all API consumers and their keys
getConsumers: async (context) => {
// Implement fetching consumers from your storage
// Each consumer can have multiple API keys
return consumers;
},
// Create a new API key for a consumer
createKey: async ({ apiKey, context, auth }) => {
const newKey: ApiKey = {
id: crypto.randomUUID(),
key: `key-${crypto.randomUUID()}`,
description: apiKey.description,
createdOn: new Date().toISOString(),
expiresOn: apiKey.expiresOn,
};
// Save the new key to your storage
// Associate it with the current user/consumer
},
// Delete an API key
deleteKey: async (consumerId, keyId, context) => {
// Remove the key from the consumer's keys in your storage
},
// Regenerate an API key
rollKey: async (consumerId, context) => {
// Update the key value for the consumer while maintaining metadata
},
// Update consumer information
updateConsumer: async (consumer, context) => {
// Update the consumer's label or other metadata
},
// Get API usage statistics (optional)
getUsage: async (apiKeys, context) => {
// Fetch usage data for the provided API keys
},
},
};
```
---
## Document: Environment Variables
URL: /docs/guides/environment-variables
# Environment Variables
Zudoku is built on top of Vite and uses [their approach](https://vite.dev/guide/env-and-mode) for
managing environment variables.
Zudoku exposes environment variables under the `import.meta.env` object as strings automatically.
To prevent accidentally leaking environment variables to the client, only variables prefixed with
`ZUDOKU_PUBLIC_` are exposed to your Zudoku-processed code.
:::warning{title="Security Notice"}
Environment variables prefixed with `ZUDOKU_PUBLIC_` will be exposed to the client-side code and
visible in the browser. Never use this prefix for sensitive information like API keys, passwords, or
other secrets.
:::
## Local Env Files
When developing locally, you can create a `.env` file in the root of your project and add
environment-specific variables. See the
[Vite documentation](https://vite.dev/guide/env-and-mode.html#env-files) for more information on
supported files.
Here is an example of a `.env.local` file:
```sh
ZUDOKU_PUBLIC_PAGE_TITLE=My Page Title
```
You can access this variable in your application like this:
```ts
const title = import.meta.env.ZUDOKU_PUBLIC_PAGE_TITLE;
```
## Configuration Files
Environment variables can also be used in your configuration files. When referencing environment
variables in your configuration files, you can use `process.env` directly.
```ts
import type { ZudokuConfig } from "zudoku";
const config: ZudokuConfig = {
authentication: {
type: "auth0",
clientId: process.env.ZUDOKU_PUBLIC_AUTH_CLIENT_ID,
domain: process.env.ZUDOKU_PUBLIC_AUTH_DOMAIN,
},
};
```
## React Components
If you need to access environment variables inside a custom react component, you can access them via
`import.meta.env`. Public environment variables are inlined during the build process.
```tsx
import React from "react";
export const MyComponent = () => {
return
{import.meta.env.ZUDOKU_PUBLIC_PAGE_TITLE}
;
};
```
## IntelliSense for TypeScript
By default, Zudoku provides type definitions for `import.meta.env` in `zudoku/client.d.ts`. While
you can define more custom env variables in `.env.[mode]` files, you may want to get TypeScript
IntelliSense for user-defined env variables that are prefixed with `ZUDOKU_PUBLIC_`.
To achieve this, you can create a `zudoku-env.d.ts` in the src directory, then augment
`ImportMetaEnv` like this:
```typescript
///
interface ImportMetaEnv {
readonly ZUDOKU_PUBLIC_APP_TITLE: string;
// more env variables...
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
```
:::warning{title="Imports will break type augmentation"}
If the `ImportMetaEnv` augmentation does not work, make sure you do not have any import statements
in `vite-env.d.ts`. A helpful explanation can be found on
[this StackOverflow reply](https://stackoverflow.com/a/51114250).
:::
---
## Document: Custom pages
URL: /docs/guides/custom-pages
# Custom pages
If you want to include pages in your documentation that have greater flexibility than MDX pages, it
is possible to include custom pages of your own.
These pages are typically built using standard React markup and can borrow from a set of prebuilt
components that Zudoku already has such as buttons, links and headers.
Start by creating the page you want to add.
## Setup a custom page
Each custom page is a page component of its own and lives in a `src` directory at the root of your
project. Let's create the `` component as an example.
From the root of your project run this command:
```bash
touch src/MyCustomPage.tsx
```
You can now open `/src/MyCustomPage.tsx` in the editor of your choice. It will be empty.
Copy and paste this code to implement the page:
```tsx
import { Button, Head, Link } from "zudoku/components";
export const MyCustomPage = () => {
return (
My Custom Page
Welcome to MyCustomPage
);
};
```
## Configuration
In the [Zudoku Configuration](../configuration/overview.md) you will need to do the following:
### Change Your Config Extension
In order to embed `jsx`/`tsx` components into your Zudoku config, you will need to change your file
extension from `ts` to `tsx` (or `js` to `jsx` if not using TypeScript).
```
zudoku.config.ts -> zudoku.config.tsx
```
### Import Your Module
Import the `` component that you created.
```typescript
import { MyCustomPage } from "./src/MyCustomPage";
```
### Add a navigation entry
Add a `custom-page` item to the `navigation` configuration. Each page you want to add to the site
must be its own object.
The `path` key can be set to whatever you like. This will appear as part of the URL in the address
bar of the browser.
The `element` key references the name of the custom page component that you want to load.
```typescript
{
// ...
navigation: [
{
type: "custom-page",
path: "/a-custom-page",
element: ,
},
]
// ...
}
```
This configuration will allow Zudoku to load the contents of the `` component when a
user clicks on a link that points to `/a-custom-page`.
## Troubleshooting
### Updating Your `tsconfig.json`
Your `include` property in `tsconfig.json` should automatically be updated to reflect the new custom
pages, but in case it isn't, it should look like this:
```json
{
...
"include": ["src", "zudoku.config.tsx", "src/MyCustomPage.tsx"]
}
```
---
## Document: Hooks
Reference for the React hooks exported by Zudoku.
URL: /docs/extending/hooks
# Hooks
Zudoku exposes a set of React hooks that let you read and interact with the runtime state of your
site from custom components, MDX pages, plugins, and [slots](../configuration/slots.mdx). All hooks
are available from the `zudoku/hooks` entry point.
```typescript
import {
useAuth,
useVerifiedEmail,
useRefreshUserProfile,
useZudoku,
useCache,
useEvent,
useExposedProps,
useTheme,
useMDXComponents,
} from "zudoku/hooks";
```
## `useAuth`
The `useAuth` hook is the primary way to interact with authentication in Zudoku. It returns the
current auth state along with the actions needed to sign users in and out. It works with any of the
supported [authentication providers](../configuration/authentication.md).
```typescript
import { useAuth } from "zudoku/hooks";
const { isAuthEnabled, isAuthenticated, isPending, profile, login, logout, signup } = useAuth();
```
### Returned values
| Property | Type | Description |
| ----------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| `isAuthEnabled` | `boolean` | `true` if an `authentication` provider is configured in `zudoku.config.ts`. When `false`, the action methods will throw if called. |
| `isAuthenticated` | `boolean` | Whether a user is currently signed in. |
| `isPending` | `boolean` | `true` while the provider is still initializing or restoring a session. Use this to render loading states and avoid flashing UI. |
| `profile` | `UserProfile \| null` | The authenticated user's profile, or `null` when signed out. See [User profile](#user-profile). |
| `providerData` | `ProviderData \| null` | Raw provider-specific data (for example the Supabase session or Firebase user). Useful when you need access to provider-only features. |
| `login` | `(options?: AuthActionOptions) => Promise` | Starts the sign-in flow. Redirects back to the current page by default. |
| `logout` | `() => Promise` | Signs the user out. |
| `signup` | `(options?: AuthActionOptions) => Promise` | Starts the sign-up flow, if the provider supports it. Redirects back to the current page by default. |
`AuthActionOptions` accepts:
```typescript
type AuthActionOptions = {
/** URL to redirect to after the action completes. Defaults to the current page. */
redirectTo?: string;
/** Replace the current history entry instead of pushing a new one. */
replace?: boolean;
};
```
### User profile
When `isAuthenticated` is `true`, `profile` is populated with the fields returned by the provider's
user info endpoint:
```typescript
type UserProfile = {
sub: string;
email: string | undefined;
emailVerified: boolean;
name: string | undefined;
pictureUrl: string | undefined;
// Any additional claims returned by the provider
[key: string]: string | boolean | undefined;
};
```
### Example: sign-in button
```tsx
import { useAuth } from "zudoku/hooks";
import { Button } from "zudoku/ui/Button.js";
export const AuthButton = () => {
const { isAuthenticated, isPending, profile, login, logout } = useAuth();
if (isPending) {
return ;
}
if (!isAuthenticated) {
return ;
}
return (
Hi, {profile?.name ?? profile?.email}
);
};
```
### Example: gating content
```tsx
import { useAuth } from "zudoku/hooks";
export const PremiumContent = ({ children }: { children: React.ReactNode }) => {
const { isAuthenticated, isPending, login } = useAuth();
if (isPending) return null;
if (!isAuthenticated) {
return (
);
}
return <>{children}>;
};
```
For route-level gating, prefer the declarative
[protected routes](../configuration/protected-routes.md) configuration.
## `useVerifiedEmail`
Returns the current user's email verification state and exposes helpers to refresh it or request a
new verification email. Use this in components that show verification banners or block actions until
the user has verified their address.
```typescript
import { useVerifiedEmail } from "zudoku/hooks";
const { email, isVerified, supportsEmailVerification, refresh, requestEmailVerification } =
useVerifiedEmail();
```
| Property | Type | Description |
| --------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
| `email` | `string \| undefined` | The user's email address, if any. |
| `isVerified` | `boolean \| undefined` | Whether the provider reports the email as verified. `undefined` when no profile is available (e.g. signed out or pending). |
| `supportsEmailVerification` | `boolean` | `true` when the provider implements a `requestEmailVerification` method. |
| `refresh` | `() => void` | Re-fetch the user profile from the provider — useful after the user verifies in another tab. |
| `requestEmailVerification` | `(options?: AuthActionOptions) => Promise` | Triggers the provider's "resend verification email" flow. |
The hook automatically refreshes the profile when the window regains focus so `isVerified` updates
after the user completes verification elsewhere.
## `useRefreshUserProfile`
Low-level hook that re-fetches the authenticated user's profile from the configured provider. Most
applications do not need to call this directly — `useAuth` already invokes it, and
`useVerifiedEmail` exposes a more ergonomic `refresh()`. Reach for it when you need fine-grained
control over the underlying React Query.
```typescript
import { useRefreshUserProfile } from "zudoku/hooks";
const { refetch, isFetching } = useRefreshUserProfile({ refetchOnWindowFocus: "always" });
```
It returns the full
[React Query `UseQueryResult`](https://tanstack.com/query/latest/docs/framework/react/reference/useQuery).
## `useZudoku`
Returns the global [`ZudokuContext`](../custom-plugins.md) — the object that holds navigation, auth,
plugins, the React Query client, and user-configured options. Use it when you need access to app
configuration that isn't exposed by a more specific hook.
```typescript
import { useZudoku } from "zudoku/hooks";
const { options, navigation, queryClient, addEventListener, emitEvent } = useZudoku();
```
Must be called inside the `ZudokuProvider` (i.e. inside any Zudoku page or slot). Calling it outside
throws.
## `useEvent`
Subscribes to Zudoku events (such as navigation or authentication changes) with automatic cleanup.
See the [Events](./events.md) page for the full guide and the list of available events.
```typescript
import { useEvent } from "zudoku/hooks";
// Access the latest event payload. When called without a callback, useEvent
// returns the event's argument tuple — destructure it to get the payload.
const [locationEvent] = useEvent("location") ?? [];
// Or transform the payload with a callback
const pathname = useEvent("location", ({ to }) => to.pathname);
// Or run a side effect only
useEvent("auth", ({ prev, next }) => {
if (!prev.isAuthenticated && next.isAuthenticated) {
trackSignIn(next.profile);
}
});
```
## `useExposedProps`
Convenience wrapper around React Router's hooks. Returns the props Zudoku passes to `custom-page`
navigation entries, so you get the same shape whether you're writing a page component or a slot.
```typescript
import { useExposedProps } from "zudoku/hooks";
const { location, navigate, params, searchParams, setSearchParams } = useExposedProps();
```
## `useCache`
Invalidates Zudoku's internal React Query caches. Today this supports `API_IDENTITIES`, which is
useful when you change something that affects the identities available to the API playground (for
example after a user creates a new API key).
```typescript
import { useCache } from "zudoku/hooks";
const { invalidateCache } = useCache();
await invalidateCache("API_IDENTITIES");
```
## Re-exported hooks
For convenience, Zudoku re-exports two hooks from its underlying libraries:
- `useTheme` from [`next-themes`](https://github.com/pacocoursey/next-themes#usetheme) — read or
change the active color scheme (`light`, `dark`, or `system`).
- `useMDXComponents` from [`@mdx-js/react`](https://mdxjs.com/packages/react/) — access the MDX
component mapping when rendering MDX content inside a custom component.
```typescript
import { useMDXComponents, useTheme } from "zudoku/hooks";
```
---
## Document: Events
URL: /docs/extending/events
# Events
Zudoku provides an events system that allows plugins to react to various application events. This
system enables you to build dynamic features that respond to user interactions and application state
changes.
## Available Events
Currently, Zudoku supports the following events:
### location
```typescript
type LocationEvent = (e: { from?: Location; to: Location }) => void;
```
Emitted when the user navigates to a different route. Provides both the previous (`from`) and
current (`to`)
[Location objects](https://api.reactrouter.com/v7/interfaces/react_router.Location.html) from
react-router.
Note that the `from` location will be undefined on the initial page load.
## Consuming Events in Plugins
To consume events in your plugin, you can implement the events property in your plugin. This is
useful for performing actions like sending analytics events or anything else that's not directly
related to the UI.
```typescript
import { ZudokuPlugin, ZudokuEvents } from "zudoku";
const navigationLoggerPlugin: ZudokuPlugin = {
events: {
location: ({ from, to }) => {
if (!from) {
console.log(`Initial navigation to: ${to.pathname}`);
} else {
console.log(`User navigated from: ${from.pathname} to: ${to.pathname}`);
}
},
},
};
```
### Example in Zudoku Config
In your `zudoku.config.ts`, you can define the events like this:
```typescript
export default {
plugins: [
{
events: {
location: ({ from, to }) => {
if (!from) return;
// E.g. send an analytics event
sendAnalyticsEvent({
from: from.pathname,
to: to.pathname,
});
},
},
},
],
};
```
## Using Events in Components
Zudoku provides a convenient `useEvent` hook to subscribe to events in your React components. The
hook can be used in three different ways:
### 1. Getting the Latest Event Data
If you just want to access the latest event data without a callback:
```typescript
import { useEvent } from "zudoku/hooks";
function MyComponent() {
const locationEvent = useEvent("location");
return
Current path: {locationEvent?.to.pathname}
;
}
```
### 2. Using Event Data in a Component
If you want to transform the event data, return a value from the callback:
```typescript
import { useEvent } from "zudoku/hooks";
function MyComponent() {
const pathname = useEvent("location", ({ to }) => to.pathname);
return
Current path: {pathname}
;
}
```
### 3. Using a Callback for Side Effects
If you just want to perform side effects when the event occurs:
```typescript
import { useEvent } from "zudoku/hooks";
function MyComponent() {
useEvent("location", ({ from, to }) => {
if (from) {
console.log(`Navigation: ${from.pathname} → ${to.pathname}`);
}
// No return value needed for side effects
});
return
My Component
;
}
```
The `useEvent` hook automatically handles subscription and cleanup in the React lifecycle, making it
easy to work with events in your components.
---
## Document: Zuplo
URL: /docs/deploy/zuplo
# Zuplo
[Zuplo](https://zuplo.com) offers a fully managed hosting solution for your Zudoku-powered
documentation, combined with a complete API gateway and management platform. When you deploy to
Zuplo, you get more than just hosting—you get a production-ready developer portal with built-in API
key management, rate limiting, analytics, and more.
## Why Deploy to Zuplo?
Deploying your Zudoku documentation to Zuplo provides several advantages:
### Fully Managed Hosting
- **Global Edge Network**: Your documentation is served from 300+ data centers worldwide, ensuring
fast load times for users everywhere
- **Automatic SSL**: SSL certificates are automatically provisioned and renewed—no configuration
needed
- **Custom Domains**: Easily configure custom domains for both your API gateway and developer portal
- **Zero Infrastructure**: No servers to manage, scale, or maintain
### Integrated API Gateway
When your documentation lives alongside your API gateway, you unlock powerful capabilities:
- **API Key Management**: Let developers self-serve their API keys directly from the documentation
portal
- **Rate Limiting**: Protect your APIs with precise, edge-deployed rate limiting
- **Authentication**: Support for API keys, JWT, OAuth, mTLS, and more
- **Real-time Analytics**: Monitor API usage and performance in real-time
### Developer Portal Features
- **OpenAPI Integration**: Documentation is automatically generated and stays in sync with your
OpenAPI specifications
- **Self-Service Keys**: Developers can create, view, and manage their API keys without waiting for
manual provisioning
- **Usage Analytics**: Developers can see their own API usage and debug issues directly in the
portal
- **Monetization Ready**: Create pricing plans and usage limits for your API products
## Getting Started
To deploy your Zudoku documentation to Zuplo:
1. **Create a Zuplo Account**: Sign up for free at [zuplo.com](https://zuplo.com)
2. **Create a New Project**: Set up a new project in the Zuplo Portal
3. **Configure Your Developer Portal**: Enable the developer portal and customize it with your
Zudoku configuration
4. **Deploy**: Push your changes and Zuplo handles the rest—your documentation is live globally in
seconds
For detailed setup instructions, see the
[Zuplo Developer Portal documentation](https://zuplo.com/docs/dev-portal/introduction).
## Custom Domains
You can configure custom domains for your developer portal in the Zuplo Portal:
1. Go to **Settings** → **Custom Domain** in your project
2. Click **Add New Custom Domain**
3. Select **Developer Portal** as the domain type
4. Enter your domain (e.g., `docs.example.com`)
5. Add the CNAME record to your DNS provider:
```
CNAME docs.example.com cname.zuplodocs.com
```
6. Redeploy your project to activate the custom domain
SSL certificates are automatically provisioned and renewed for your custom domains.
## Static Assets
Place static files (images, PDFs, etc.) in a `public` directory in your project root. These files
are served at the root path and can be referenced with absolute paths:
```
your-project/
├── public/
│ ├── images/
│ │ └── diagram.png
│ └── documents/
│ └── api-spec.pdf
└── ...
```
Reference them in your documentation:
```markdown

```
## Pricing
Zuplo offers multiple pricing tiers to fit your needs:
- **Free**: Get started at no cost with essential features
- **Builder**: For individual developers and small teams
- **Enterprise**: Custom solutions with advanced analytics and support
Visit [zuplo.com/pricing](https://zuplo.com/pricing) for current pricing details.
## Learn More
- [Zuplo Documentation](https://zuplo.com/docs)
- [Developer Portal Guide](https://zuplo.com/docs/dev-portal/introduction)
- [API Key Management](https://zuplo.com/features/api-key-management)
- [Custom Domains](https://zuplo.com/docs/articles/custom-domains)
---
## Document: Vercel
URL: /docs/deploy/vercel
# Vercel
[Vercel](https://vercel.com) offers multiple ways to deploy to its service, including via GitHub,
and their CLI. You can read more about them in their
[Deployments Overview](https://vercel.com/docs/deployments/overview).
## Prerequisites
To deploy to Vercel you will need:
- A Vercel account (free)
- A GitHub account (if deploying via GitHub)
- The Vercel CLI
Deploying your Zudoku powered documentation using the Vercel CLI is what we will cover in this
guide.
## Install the CLI
To get started you need to install the CLI:
```bash
npm i -g vercel
```
## Setup a new project
Next, set up a new Vercel project in the root of your docs:
```bash
vercel
```
This command will set up everything that is needed to deploy your documentation to Vercel.
It will ask some specific questions including the project name and where the code is located. You
can answer however you like for these.
When you get to this step:
```ansi
No framework detected. Default Project Settings:
- Build Command: `npm run vercel-build` or `npm run build`
- Development Command: None
- Install Command: `yarn install`, `pnpm install`, `npm install`, or `bun install`
- Output Directory: `public` if it exists, or `.`
? Want to modify these settings? (y/N)
```
Answer _Yes_ and select to modify the Output Directory.
By default Vercel looks for a directory named `public`, but the Zudoku build will be found in
`dist`. Set the output directory like this:
```ansi
? What's your Output Directory? dist
```
After this is complete, your site will build and Vercel will respond with the URL for you to test
it.
:::tip{title="Clean URLs"}
You will almost certainly want to enable clean URLs for your site. This will remove the `.html`
extension from your URLs. You can do this by adding a `cleanUrls` property to your `vercel.json`
file. See the
[Vercel Configuration](https://vercel.com/docs/projects/project-configuration#cleanurls) for more
information.
:::
:::caution{title="Redirects"}
If you have redirects configured in your Zudoku configuration, you will need to also add those to
your `vercel.json` file. See the
[Vercel Configuration](https://vercel.com/docs/projects/project-configuration#redirects) for more
information.
This is a current limitation. See [#115](https://github.com/zuplo/zudoku/issues/151).
:::
## Accurate Last Modified Dates
If you have enabled the [`showLastModified`](/docs/configuration/docs#showlastmodified) option,
Zudoku automatically tracks the last modified date of your documentation pages using Git history.
However, Vercel performs shallow clones by default (only fetching the last 10 commits), which can
result in inaccurate "Last Modified" dates for pages that haven't been updated recently.
To ensure accurate last modified dates, add the `VERCEL_DEEP_CLONE` environment variable to your
Vercel project:
1. Go to your project settings in Vercel
2. Navigate to "Environment Variables"
3. Add a new variable: `VERCEL_DEEP_CLONE=true`
4. Save and redeploy your site
This will enable full Git history during builds, ensuring all pages show their correct last modified
dates. The impact on build time is minimal (typically 5-20 seconds on the first build), and
subsequent builds benefit from caching.
---
## Document: GitHub Pages
URL: /docs/deploy/github-pages
# GitHub Pages
[GitHub Pages](https://pages.github.com/) is a great way to publish your documentation, especially
if you are using GitHub for source control.
## Prerequisites
To publish your site to GitHub Pages you will need:
- A GitHub account
## Deploying to GitHub Pages
Use the
[GitHub Actions Workflow](https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site#publishing-with-a-custom-github-actions-workflow).
You might need to configure the `basePath` in your zudoku.config.ts depending on how your site is
hosted. For instance, if your site is available at username.github.io/your-repo/ you would set the
`basePath` to `/your-repo`.
```typescript
import type { ZudokuConfig } from "zudoku";
const config: ZudokuConfig = {
basePath: "/your-repo",
//...
};
```
When `basePath` is set, Zudoku writes the site into `dist//`. Upload that nested directory
as your Pages artifact, not `dist/` itself. For example in a GitHub Actions workflow:
```yaml
- uses: actions/upload-pages-artifact@v3
with:
path: dist/your-repo
```
Uploading the outer `dist/` would nest the site under `/your-repo/your-repo/` and every asset
would 404.
## Accurate Last Modified Dates
If you have enabled the [`showLastModified`](/docs/configuration/docs#showlastmodified) option,
Zudoku automatically tracks the last modified date of your documentation pages using Git history.
However, GitHub Actions performs shallow clones by default, which can result in inaccurate "Last
Modified" dates for pages that haven't been updated recently.
To ensure accurate last modified dates, configure the `actions/checkout` step to fetch the full
history:
```yaml
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch all history for all branches and tags
```
For more details, see the
[actions/checkout documentation](https://github.com/actions/checkout#fetch-all-history-for-all-tags-and-branches).
---
## Document: Upload/FTP
URL: /docs/deploy/direct-upload
# Upload/FTP
Zudoku can produce a build of static HTML, JavaScript and CSS files that you can deploy directly to
your own server.
## Build locally
To prepare the files you need to upload to your server, you will need to use the build command.
```bash
npm run build
```
Once complete, you will see a new `dist` folder in the root of your project that includes the files
you need to upload.
---
## Document: Cloudflare Pages
URL: /docs/deploy/cloudflare-pages
# Cloudflare Pages
[Pages](https://developers.cloudflare.com/pages) is a low configuration way of publishing your
documentation.
## Prerequisites
To publish your site to Cloudflare Pages you will need:
- A Cloudflare account
Optionally, you may also need:
- A GitHub or GitLab account (optional)
## Deploying to Pages
Cloudflare offers three different ways to create Pages sites.
1. Using the [Cloudflare CLI](https://developers.cloudflare.com/pages/get-started/c3/)
2. Directly uploading the files using
[drag and drop](https://developers.cloudflare.com/pages/get-started/direct-upload/#drag-and-drop),
or their
[Wrangler CLI](https://developers.cloudflare.com/pages/get-started/direct-upload/#wrangler-cli)
3. Integration with
[GitHub or GitLab](https://developers.cloudflare.com/pages/get-started/git-integration/)
It's up to you which approach you choose but the fastest approach is to use the Wrangler CLI.
## Deploy using Wrangler
Wrangler makes light work of deploying to Cloudflare Pages, but first you need to get your files in
order.
Build the static version of your documentation site by running:
```bash
npm run build
```
This will generate a new folder called `dist` that contains all the files that you need to deploy.
Next, if you don't already have one, create a new Pages project:
```bash
npx wrangler pages project create
```
Finally, deploy the files to the new project:
```bash
npx wrangler pages deploy ./dist
```
Within a few seconds the site will be live and viewable at `.pages.dev`.
## Accurate Last Modified Dates
If you have enabled the [`showLastModified`](/docs/configuration/docs#showlastmodified) option,
Zudoku automatically tracks the last modified date of your documentation pages using Git history.
However, Cloudflare Pages performs shallow clones by default, which can result in inaccurate "Last
Modified" dates for pages that haven't been updated recently.
To ensure accurate last modified dates when using Git integration with Cloudflare Pages, modify your
build command to fetch the full history before building:
```bash
git fetch --unshallow && npm run build
```
This command converts the shallow clone to a full clone before running your build, ensuring all
pages show their correct last modified dates.
---
## Document: Apache & Nginx
URL: /docs/deploy/apache-nginx
# Apache & Nginx
Zudoku generates static HTML files for each page during build. Your server must be configured to
serve these files correctly.
## Apache
Create a `.htaccess` file in your document root (alongside `index.html`):
```apache
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^(.*)$ $1.html [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.html [L]
```
Requires `mod_rewrite` enabled and `AllowOverride All` in your Apache configuration.
## Nginx
Add a `try_files` directive to your server block:
```nginx
server {
listen 80;
server_name example.com;
root /var/www/html;
index index.html;
location / {
try_files $uri $uri.html $uri/ /index.html;
}
}
```
---
## Document: Font & Typography
Learn how to customize fonts and typography in Zudoku using predefined Google Fonts, custom font URLs, or local fonts for sans, serif, and monospace text.
URL: /docs/customization/fonts
# Font & Typography
Zudoku allows you to customize fonts for text (`sans`), serif content (`serif`), and code (`mono`).
You can use predefined Google Fonts, external sources, or local fonts.
## Predefined Google Fonts
The easiest way to use fonts is with predefined Google Fonts. Simply specify the font name as a
string:
```ts title=zudoku.config.ts
const config = {
theme: {
fonts: {
sans: "Inter",
serif: "Merriweather",
mono: "JetBrains Mono",
},
},
};
```
### Available Google Fonts
The following fonts are available as predefined options:
**Sans Serif:** Inter, Roboto, Open Sans, Poppins, Montserrat, Outfit, Plus Jakarta Sans, DM Sans,
IBM Plex Sans, Geist, Oxanium, Space Grotesk
**Serif:** Architects Daughter, Merriweather, Playfair Display, Lora, Source Serif Pro, Libre
Baskerville
**Monospace:** JetBrains Mono, Fira Code, Source Code Pro, IBM Plex Mono, Roboto Mono, Space Mono,
Geist Mono
## Custom Font URLs
For more control or to use fonts not in the predefined list, you can specify a custom font URL:
```ts title=zudoku.config.ts
const config = {
theme: {
fonts: {
sans: {
url: "https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap",
fontFamily: "Roboto, sans-serif",
},
mono: {
url: "https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;700&display=swap",
fontFamily: "Roboto Mono, monospace",
},
},
},
};
```
## Local Fonts
To use local fonts, add them to the `public` folder and create a `fonts.css` file:
```css
@font-face {
font-family: "CustomFont";
font-style: normal;
font-weight: 400;
src: url("/custom-font-400.woff2") format("woff2");
}
```
Then reference the local CSS file:
```ts title=zudoku.config.ts
const config = {
theme: {
fonts: {
sans: {
url: "/fonts.css",
fontFamily: "CustomFont, sans-serif",
},
},
},
};
```
## Mixed Configuration
You can mix predefined fonts with custom fonts:
```ts title=zudoku.config.ts
const config = {
theme: {
fonts: {
sans: "Inter", // Predefined Google Font
serif: {
// Custom font
url: "/custom-serif.css",
fontFamily: "CustomSerif, serif",
},
mono: "Fira Code", // Predefined Google Font
},
},
};
```
---
## Document: Colors & Theme
Customize your Zudoku site's colors and theme with flexible options, including light/dark modes, custom CSS, and shadcn registry integration.
URL: /docs/customization/colors-theme
# Colors & Theme
Zudoku provides flexible theming options allowing you to customize colors, import themes from
[shadcn registries](https://ui.shadcn.com/docs/registry), and add custom CSS. You can create
cohesive light and dark mode experiences that match your brand.
:::tip
Try out the interactive [Theme Playground](/docs/theme-playground) to experiment with colors and see
real-time previews of your theme changes.
:::
The theme system is built on [shadcn/ui theming](https://ui.shadcn.com/docs/theming) and
[Tailwind v4](https://tailwindcss.com), giving us a great foundation to build upon:
- **CSS variables** match `shadcn/ui` conventions
- **Tailwind v4** CSS variable system for modern styling
- **Theme editors** like [tweakcn](https://tweakcn.com/) work out of the box
- **Shadcn registries** are supported
## Custom Colors
You can manually define colors for both light and dark modes, either by extending the
[default theme](#default-theme) or creating a completely custom theme. Colors can be specified as
hex values, RGB, HSL, OKLCH, etc. - basically anything that is supported by
[Tailwind CSS](https://tailwindcss.com):
```ts title=zudoku.config.ts
const config = {
theme: {
light: {
background: "#ffffff",
foreground: "#020817",
card: "#ffffff",
cardForeground: "#020817",
popover: "#ffffff",
popoverForeground: "#020817",
primary: "#0284c7",
primaryForeground: "#ffffff",
secondary: "#f1f5f9",
secondaryForeground: "#020817",
muted: "#f1f5f9",
mutedForeground: "#64748b",
accent: "#f1f5f9",
accentForeground: "#020817",
destructive: "#ef4444",
destructiveForeground: "#ffffff",
border: "#e2e8f0",
input: "#e2e8f0",
ring: "#0284c7",
radius: "0.5rem",
},
dark: {
background: "#020817",
foreground: "#f8fafc",
card: "#020817",
cardForeground: "#f8fafc",
popover: "#020817",
popoverForeground: "#f8fafc",
primary: "#0ea5e9",
primaryForeground: "#f8fafc",
secondary: "#1e293b",
secondaryForeground: "#f8fafc",
muted: "#1e293b",
mutedForeground: "#94a3b8",
accent: "#1e293b",
accentForeground: "#f8fafc",
destructive: "#ef4444",
destructiveForeground: "#f8fafc",
border: "#1e293b",
input: "#1e293b",
ring: "#0ea5e9",
radius: "0.5rem",
},
},
};
```
## Available Theme Variables
| Variable | Description |
| ----------------------- | ------------------------------------- |
| `background` | Main background color |
| `foreground` | Main text color |
| `card` | Card background color |
| `cardForeground` | Card text color |
| `popover` | Popover background color |
| `popoverForeground` | Popover text color |
| `primary` | Primary action color |
| `primaryForeground` | Text color on primary backgrounds |
| `secondary` | Secondary action color |
| `secondaryForeground` | Text color on secondary backgrounds |
| `muted` | Muted/subtle background color |
| `mutedForeground` | Text color for muted elements |
| `accent` | Accent color for highlights |
| `accentForeground` | Text color on accent backgrounds |
| `destructive` | Color for destructive actions |
| `destructiveForeground` | Text color on destructive backgrounds |
| `border` | Border color |
| `input` | Input field border color |
| `ring` | Focus ring color |
| `radius` | Border radius value |
:::note
While shadcn/ui defines additional theme variables, Zudoku currently uses only these core variables.
:::
## shadcn Registry Integration
The easiest way to customize your theme is by using a Shadcn registry theme. For example you can use
the great [tweakcn](https://tweakcn.com/) visual theme editor.
### Using tweakcn Themes
1. Visit [tweakcn.com](https://tweakcn.com/) to select a preset or customize your theme visually

1. Copy the registry URL from the "Copy" section

1. Add it to your configuration:
```ts title=zudoku.config.ts
const config = {
theme: {
registryUrl: "https://tweakcn.com/r/themes/northern-lights.json",
},
};
```
1. The theme will then be automatically imported with all color variables, fonts, and styling
configured for you 🚀

You can still override specific values if needed:
```ts title=zudoku.config.ts
const config = {
theme: {
registryUrl: "https://tweakcn.com/api/registry/theme/xyz123",
// Override specific colors
light: {
primary: "#0066cc",
},
dark: {
primary: "#3399ff",
},
},
};
```
Alternatively, paste the copied CSS into a stylesheet and import it from your config:
```css title=styles.css
/* Copied CSS code */
```
```ts title=zudoku.config.ts
import "./styles.css";
```
## Custom CSS
The recommended way to add custom styles is to write a `.css` file alongside your config and import
it. This gives you HMR during development, syntax highlighting, autocompletion, and lets you split
styles across files as your site grows.
```css title=styles.css
.custom {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
```
```ts title=zudoku.config.ts
import "./styles.css";
const config = {
// ...
};
```
If TypeScript reports `Cannot find module './styles.css'`, add `zudoku/client` to your tsconfig
types so CSS side-effect imports are recognized:
```json title=tsconfig.json
{
"compilerOptions": {
"types": ["zudoku/client"]
}
}
```
Projects created with `create-zudoku` include this by default.
### Inline alternatives (deprecated)
The `theme.customCss` option is deprecated and will be removed in a future release. It still accepts
a CSS string or object for backwards compatibility, but every change requires restarting the dev
server and you lose syntax highlighting, autocompletion, and HMR. Migrate to an imported `.css`
file.
```ts title=zudoku.config.ts
const config = {
theme: {
customCss: `
.custom {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
`,
},
};
```
### Enabling Code Ligatures
Zudoku disables ligatures in code blocks by default to avoid unwanted glyph joining in fonts like
Geist Mono (e.g. `---`, `###`, `|--|`). If you're using a coding font designed around ligatures
(Fira Code, JetBrains Mono, etc.), re-enable them in your CSS file:
```css title=styles.css
code,
pre,
kbd,
samp,
.shiki,
.shiki span {
font-variant-ligatures: normal;
font-feature-settings: normal;
}
```
## Default Theme
Zudoku comes with a built-in default theme based on
[shadcn/ui zinc base colors](https://ui.shadcn.com/docs/theming#zinc). If you want to start
completely from scratch without any default styling, you can disable the default theme:
```ts title=zudoku.config.ts
const config = {
theme: {
noDefaultTheme: true,
// Your custom theme configuration
},
};
```
When `noDefaultTheme` is set to `true`, no default colors or styling will be applied, giving you
complete control over your theme. Changing this requires to restart the development server.
## Complete Example
Here's a comprehensive example combining multiple theming approaches:
```ts title=zudoku.config.ts
const config = {
theme: {
// Import base theme from registry
registryUrl: "https://tweakcn.com/api/registry/theme/modern-blue",
// Override specific colors
light: {
primary: "#0066cc",
accent: "#f0f9ff",
},
dark: {
primary: "#3399ff",
accent: "#0c1b2e",
},
// Custom fonts
fonts: {
sans: "Inter",
mono: "JetBrains Mono",
},
// Additional custom styling
customCss: {
".hero-section": {
background: "var(--primary)",
color: "var(--primary-foreground)",
padding: "2rem",
"border-radius": "var(--radius)",
},
},
},
};
```
This configuration imports a base theme, customizes colors for both light and dark modes, sets
fonts, and adds custom component styling.
---
## Document: Vite Config
URL: /docs/configuration/vite-config
# Vite Config
Zudoku is built on top of [Vite](https://vite.dev/) and can be customized using a
[Vite configuration file](https://vite.dev/config/) if advanced functionality is required.
Not all configurations are supported in Zudoku, but common tasks like adding plugins will generally
work as expected. Simply create a `vite.config.ts` file in the root of your project and set the
configuration options as needed.
Zudoku will automatically pick up the configuration file and will use it to augment the built-in
configuration.
You can find an
[example project](https://github.com/zuplo/zudoku/tree/main/examples/with-vite-config) on GitHub
that demonstrates how to use a custom Vite configuration with Zudoku.
---
## Document: Slots
Learn how to use slots to inject custom content into predefined locations throughout your Zudoku site.
URL: /docs/configuration/slots
# Slots
Slots provide a powerful way to inject custom content into predefined locations throughout Zudoku.
They allow you to extend the default layout and functionality without modifying the core components.
## Configuration
You can define slots in your `zudoku.config.tsx` file using the `slots` property:
```tsx
import type { ZudokuConfig } from "zudoku";
import { Button } from "zudoku/ui/Button.js";
const config: ZudokuConfig = {
// ... other config
slots: {
"head-navigation-end": () => (
),
"footer-before":
Custom footer content
,
},
};
export default config;
```
## Slot Types
Slots accept either:
- **React components/elements**: JSX elements
- **Function components**: Functions that return JSX elements and receive routing props
```tsx
slots: {
// JSX element
"footer-after": ,
// Function with access to routing props
"head-navigation-end": ({ navigate, location, searchParams }) => (
),
}
```
Functions receive an object with routing properties:
- `location` - Current route location
- `navigate` - Navigation function
- `searchParams` - URL search parameters
- `setSearchParams` - Function to update search parameters
- `params` - Route parameters
## Type Safety
Zudoku provides full TypeScript support for slot names. All predefined slot names will show up with
autocomplete when you type them in your configuration.
## Advanced Usage
For more advanced slot usage, including programmatic slot management, dynamic content, and adding
custom slot names, see the [Slot Component](/docs/components/slot) documentation.
## Examples
### Adding Social Links to Header
```tsx
slots: {
"head-navigation-end": () => (
),
}
```
---
## Document: /docs/configuration/site
Customize your Zudoku site's branding, logo, banner, and layout options with detailed configuration examples and guidance.
URL: /docs/configuration/site
# Branding & Layout
We offer you to customize the main aspects of your Zudoku site's appearance and behavior.
## Branding
**Title**, **logo** can be configured in under the `site` property:
```tsx title=zudoku.config.tsx
const config = {
site: {
title: "My API Documentation",
logo: {
src: {
light: "/path/to/light-logo.png",
dark: "/path/to/dark-logo.png",
},
alt: "Company Logo",
href: "/",
},
// Other options...
},
};
```
### Available Options
#### Title
Set the title of your site next to the logo in the header:
```tsx title=zudoku.config.tsx
{
site: {
title: "My API Documentation",
}
}
```
#### Logo
Configure the site's logo with different versions for light and dark themes:
```tsx title=zudoku.config.tsx
{
site: {
logo: {
src: {
light: "/light-logo.png",
dark: "/dark-logo.png"
},
alt: "Company Logo",
width: "120px", // optional width
href: "/", // optional link target (defaults to "/")
reloadDocument: true, // optional, defaults to true
}
}
}
```
The `reloadDocument` option controls whether clicking the logo triggers a full page reload (`true`,
the default) or uses client-side SPA navigation (`false`). A full reload is useful when your landing
page is served by a different system (e.g. a CMS) outside of Zudoku.
#### Direction (RTL/LTR)
Set the text direction for your site. This is useful for right-to-left languages:
```tsx title=zudoku.config.tsx
{
site: {
dir: "rtl", // "ltr" (default) or "rtl"
}
}
```
#### Colors & Theme
We allow you to fully customize all colors, borders, etc - read more about it in
[Colors & Themes](/docs/customization/colors-theme)
#### Custom 404 Page
Replace the default "Page not found" page with your own component using the `notFoundPage` option:
```tsx title=zudoku.config.tsx
import { NotFound } from "./src/NotFound";
const config = {
site: {
notFoundPage: ,
},
};
```
Your component will be rendered whenever a user navigates to a route that doesn't exist. This works
in both development and production builds.
Here's an example of a custom 404 component:
```tsx title=src/NotFound.tsx
import { Button, Link } from "zudoku/components";
export const NotFound = () => (
404
Page not found
The page you're looking for doesn't exist or has been moved.
);
};
```
---
## Document: Typography
URL: /docs/components/typography
# Typography
import { Typography } from "zudoku/components";
The Typography component applies consistent prose styling to text content using
[Tailwind's typography plugin](https://github.com/tailwindlabs/tailwindcss-typography). It
automatically formats headings, paragraphs, lists, and other text elements with appropriate spacing,
## Import
```tsx
import { Typography } from "zudoku/components";
```
font sizes, and styling that adapts to both light and dark themes.
This component is particularly useful when rendering markdown content or when you need consistent
text formatting across your documentation.
## Props
```ts
type TypographyProps = {
children: React.ReactNode;
className?: string;
};
```
## Usage
Wrap any content that needs prose formatting with the Typography component. It will automatically
style headings, paragraphs, lists, and other text elements:
```tsx
import { Typography } from "zudoku/components";
Hello World
This is a paragraph
Item 1
Item 2
Item 3
;
```
## Example
Hello World
This is a paragraph
Item 1
Item 2
Item 3
---
## Document: Tooltip
URL: /docs/components/tooltip
# Tooltip
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
TooltipArrow,
} from "zudoku/ui/Tooltip";
import { Button } from "zudoku/ui/Button";
A tooltip component built on Radix UI primitives for displaying helpful information on hover or
focus.
## Import
```tsx
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
TooltipArrow,
} from "zudoku/ui/Tooltip";
```
## Components
The Tooltip component consists of several sub-components:
- `TooltipProvider` - Provides context for tooltip behavior
- `Tooltip` - The main container (root)
- `TooltipTrigger` - Element that triggers the tooltip
- `TooltipContent` - The tooltip content container
- `TooltipArrow` - Optional arrow pointing to the trigger
## Basic Usage
Add to library
```tsx
Add to library
```
## With Arrow
Add to library
```tsx
Add to library
```
## Different Sides
Top tooltip
Right tooltip
Bottom tooltip
Left tooltip
```tsx
Top tooltip
Right tooltip
Bottom tooltip
Left tooltip
```
## Global Provider
For multiple tooltips, wrap your app with `TooltipProvider`:
```tsx
function App() {
return {/* Your app content with tooltips */};
}
```
## Features
- **Keyboard Navigation**: Accessible via keyboard focus
- **Positioning**: Smart positioning to stay within viewport
- **Delay Control**: Configurable show/hide delays
- **Animation**: Smooth enter/exit animations
- **Accessibility**: Full screen reader and keyboard support
---
## Document: Textarea
URL: /docs/components/textarea
# Textarea
import { Textarea } from "zudoku/ui/Textarea";
import { Label } from "zudoku/ui/Label";
A multiline text input component for forms and user input.
## Import
```tsx
import { Textarea } from "zudoku/ui/Textarea";
```
## Basic Usage
```tsx
```
## With Label
```tsx
```
## With Text
Your message will be copied to the support team.
```tsx
Your message will be copied to the support team.
```
## Disabled State
```tsx
```
## Custom Height
```tsx
```
## Custom Styling
The Textarea component accepts all standard textarea HTML attributes and can be customized with
additional className:
```tsx
```
---
## Document: Syntax Highlight
URL: /docs/components/syntax-highlight
# Syntax Highlight
import { SyntaxHighlight } from "zudoku/ui/SyntaxHighlight";
import { Callout } from "zudoku/ui/Callout";
Add syntax highlighting to your code blocks with customizable styling and features. The component
offers extensive customization options including line numbers, copy buttons, language indicators,
and custom theming to match your brand. For simpler use cases, you can also use standard Markdown
code blocks as described in [Code Blocks](/docs/markdown/code-blocks).
:::tip
You can also use backticks to highlight code blocks in Markdown files, see
[Code Blocks](../markdown/code-blocks) for more information.
:::
## Import
```tsx
import { SyntaxHighlight } from "zudoku/ui/SyntaxHighlight";
```
## Types
```ts
type SyntaxHighlightProps = {
language?: string;
code?: string;
wrapLines?: boolean;
title?: string;
showCopy?: "hover" | "always" | "never";
showCopyText?: boolean;
children?: string;
showLanguageIndicator?: boolean;
className?: string;
showLineNumbers?: boolean;
};
```
## Usage
You can either use `children` or `code` prop to pass the code to the component.
```tsx
{`for (let i = 0; i < Infinity; i++) {
console.log(i);
}`}
```
## Result
{`for (let i = 0; i < Infinity; i++) {
console.log(i);
}`}
## Supported Languages
Here are examples for all supported languages:
### TypeScript / JavaScript / TSX
```typescript title="User.ts"
interface User {
name: string;
age: number;
}
const greet = (user: User): string => `Hello, ${user.name}!`;
```
```javascript title="fibonacci.js"
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
console.log(fibonacci(10));
```
```tsx title="App.tsx"
export const App = () => {
return
Hello, World!
;
};
```
### Markdown / MDX
```markdown title="hello.md"
# Hello World
This is **bold** and _italic_ text.
- Item 1
- Item 2
[Link to Zudoku](https://zudoku.dev)
```
```mdx title="welcome.mdx"
import { Button } from "./Button";
# Welcome to MDX
```
### JSON / YAML / TOML
```json title="package.json"
{
"name": "zudoku",
"version": "1.0.0",
"scripts": {
"dev": "vite",
"build": "vite build"
}
}
```
```yaml title="package.yml"
name: zudoku
version: 1.0.0
scripts:
dev: vite
build: vite build
```
```toml title="package.toml"
[package]
name = "zudoku"
version = "1.0.0"
[dependencies]
react = "^19.0.0"
```
### Shell / Bash / Terminal
```bash title="build.sh"
#!/bin/bash
# Install and build
npm install
if [ "$NODE_ENV" = "production" ]; then
npm run build
else
npm run dev
fi
```
```ansi title=ANSI
$ pnpm run build
> zudoku@1.0.0 build
[36mvite v7.0.5 [32mbuilding for production...[0m
[32m✓ 34 modules transformed.
[90mdist/[39m[32mindex.html [0m[90m0.46 kB[0m [33m│ gzip: 0.30 kB[0m
[90mdist/[39m[36massets/index-4sK2hL.css [0m[90m24.67 kB[0m [33m│ gzip: 4.72 kB[0m
[90mdist/[39m[35massets/index-B1tPwS.js [0m[90m143.18 kB[0m [33m│ gzip: 46.13 kB[0m
[34m✓ built in 1.23s
```
### GraphQL
```graphql title="schema.graphql"
type User {
id: ID!
name: String!
email: String!
}
type Query {
user(id: ID!): User
users: [User!]!
}
type Mutation {
createUser(name: String!, email: String!): User!
}
```
### Python
```python title="data_processor.py"
import asyncio
from typing import List, Optional
class DataProcessor:
def __init__(self, name: str):
self.name = name
async def process_items(self, items: List[str]) -> dict:
results = [await self._process(item) for item in items]
return {"processed": len(results), "items": results}
```
### C#
```csharp title="user_service.cs"
using System;
using System.Linq;
public class UserService {
private readonly List _users = new();
public async Task GetUserAsync(int id) {
await Task.Delay(100);
return _users.FirstOrDefault(u => u.Id == id);
}
}
```
### Rust
```rust title="main.rs"
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Red"), 50);
println!("Team scores: {:?}", scores);
}
```
### Ruby
```ruby title="todo_list.rb"
class TodoList
attr_reader :name, :items
def initialize(name)
@name = name
@items = []
end
def add_item(description)
@items << { description: description, completed: false }
end
end
```
### PHP
```php title="user.php"
name);
}
}
```
### HTML
```html title="index.html"
Zudoku Example
Welcome to Zudoku
Build beautiful documentation sites with ease.
Nunc nec ornare libero. Sed ultricies lorem vitae enim vestibulum, at posuere augue ullamcorper.
```
### CSS
```css title="styles.css"
.button {
background-color: #007bff;
color: white;
padding: 0.5rem 1rem;
border: none;
border-radius: 0.25rem;
cursor: pointer;
}
.button:hover {
background-color: #0056b3;
}
```
### Java
```java title="user_service.java"
import java.util.List;
import java.util.stream.Collectors;
public class UserService {
private List users;
public List getActiveUsers() {
return users.stream()
.filter(User::isActive)
.collect(Collectors.toList());
}
}
```
### Go
```go title="main.go"
package main
import "fmt"
type User struct {
ID int
Name string
Email string
}
func main() {
user := User{ID: 1, Name: "Alice", Email: "alice@example.com"}
fmt.Printf("User: %+v\n", user)
}
```
### Kotlin
```kotlin title="user.kt"
data class User(val id: Long, val name: String, val email: String)
class UserRepository {
private val users = mutableListOf()
fun addUser(user: User) {
users.add(user)
}
fun findById(id: Long): User? = users.find { it.id == id }
}
```
### Objective-C
```objc title="user.m"
#import
@interface User : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *email;
@property (nonatomic, assign) NSInteger userId;
- (instancetype)initWithName:(NSString *)name
email:(NSString *)email
userId:(NSInteger)userId;
@end
```
### Swift
```swift title="user.swift"
import Foundation
struct User: Codable {
let id: UUID
let name: String
let email: String
init(name: String, email: String) {
self.id = UUID()
self.name = name
self.email = email
}
}
```
### XML
```xml title="app.xml"
```
### C
```c title="hello.c"
#include
#include
typedef struct {
int id;
char name[50];
} User;
int main() {
User user = {1, "Alice"};
printf("User: %s (ID: %d)\n", user.name, user.id);
return 0;
}
```
### C++
```cpp title="user.cpp"
#include
#include
#include
class User {
private:
int id;
std::string name;
public:
User(int id, std::string name) : id(id), name(name) {}
void display() const {
std::cout << "User: " << name << " (ID: " << id << ")" << std::endl;
}
};
int main() {
User user(1, "Alice");
user.display();
return 0;
}
```
### Zig
```zig title="main.zig"
const std = @import("std");
const User = struct {
id: u32,
name: []const u8,
pub fn display(self: User) void {
std.debug.print("User: {s} (ID: {})\n", .{ self.name, self.id });
}
};
pub fn main() !void {
const user = User{ .id = 1, .name = "Alice" };
user.display();
}
```
### Scala
```scala title="User.scala"
case class User(id: Int, name: String, email: String)
object UserRepository {
private var users = List[User]()
def addUser(user: User): Unit = {
users = user :: users
}
def findById(id: Int): Option[User] = {
users.find(_.id == id)
}
}
```
### Dart
```dart title="user.dart"
class User {
final int id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
factory User.fromJson(Map json) {
return User(
id: json['id'],
name: json['name'],
email: json['email'],
);
}
Map toJson() => {
'id': id,
'name': name,
'email': email,
};
}
```
### Elixir
```elixir title="user.ex"
defmodule User do
defstruct [:id, :name, :email]
def new(id, name, email) do
%User{id: id, name: name, email: email}
end
def display(%User{name: name, id: id}) do
IO.puts("User: #{name} (ID: #{id})")
end
end
# Usage
user = User.new(1, "Alice", "alice@example.com")
User.display(user)
```
### OCaml
```ocaml title="user.ml"
type user = {
id : int;
name : string;
email : string;
}
let create_user id name email =
{ id; name; email }
let display_user user =
Printf.printf "User: %s (ID: %d)\n" user.name user.id
let () =
let user = create_user 1 "Alice" "alice@example.com" in
display_user user
```
### Common Lisp
```lisp title="user.lisp"
(defstruct user
id
name
email)
(defun create-user (id name email)
(make-user :id id :name name :email email))
(defun display-user (user)
(format t "User: ~A (ID: ~A)~%"
(user-name user)
(user-id user)))
;; Usage
(let ((user (create-user 1 "Alice" "alice@example.com")))
(display-user user))
```
### PowerShell
```powershell title="user.ps1"
class User {
[int]$Id
[string]$Name
[string]$Email
User([int]$id, [string]$name, [string]$email) {
$this.Id = $id
$this.Name = $name
$this.Email = $email
}
[void]Display() {
Write-Host "User: $($this.Name) (ID: $($this.Id))"
}
}
# Usage
$user = [User]::new(1, "Alice", "alice@example.com")
$user.Display()
```
---
## Document: Switch
URL: /docs/components/switch
# Switch
import { Switch } from "zudoku/ui/Switch";
import { Label } from "zudoku/ui/Label";
A switch component built on Radix UI primitives for boolean toggle input.
## Import
```tsx
import { Switch } from "zudoku/ui/Switch";
```
## Basic Usage
```tsx
```
## With Label
```tsx
```
## Checked State
```tsx
```
## Disabled State
```tsx
```
## With Description
Receive emails about new products, features, and more.
```tsx
Receive emails about new products, features, and more.
```
## Features
- **Accessibility**: Full keyboard navigation and screen reader support
- **Smooth Animation**: Smooth transition between states
- **Controlled/Uncontrolled**: Can be used with or without state management
- **Touch Friendly**: Optimized for touch interactions
---
## Document: Stepper
URL: /docs/components/stepper
# Stepper
A vertical stepper component that displays a sequence of steps with automatic numbering and visual
connections between steps. Perfect for multi-step processes, tutorials, or guided workflows.
:::note
In MDX components, the `Stepper` component is available by default and doesn't need an explicit
import.
:::
## Import
```tsx
import { Stepper } from "zudoku/ui/Stepper";
```
## Usage
Wrap Markdown lists with the `Stepper` component to create a vertical stepper like this:
```tsx
1. **Identify the Problem**
1. **Plan Your Project**
1. **Build Your Solution**
1. **Test and Deploy**
```
1. **Identify the Problem**
1. **Plan Your Project**
1. **Build Your Solution**
1. **Test and Deploy**
## Advanced Example
1. **Identify the Problem**
Begin by identifying the problem you're trying to solve. This foundational step ensures you're
building the right solution for your needs.
1. **Plan Your Project**
Define your project requirements and goals. This ensures you're building the right solution for
your needs.
:::info
Make sure you have gathered all the information you need before you start planning your project.
:::
1. **Build Your Solution**
With a solid plan in place, start implementing your solution. Break down complex tasks into
manageable pieces and track your progress.
- Create component structure
- Implement core functionality
- Add styling and polish
1. **Test and Deploy** 🚀
```sh
pnpm build
pnpm test
pnpm deploy --prod
```
**See Markdown for example above 👆**
1. **Identify the Problem**
Begin by identifying the problem you're trying to solve. This foundational step ensures you're
building the right solution for your needs.
1. **Plan Your Project**
Define your project requirements and goals. This ensures you're building the right solution for
your needs.
:::info
Make sure you have gathered all the information you need before you start planning your project.
:::
1. **Build Your Solution**
With a solid plan in place, start implementing your solution. Break down complex tasks into
manageable pieces and track your progress.
- Create component structure
- Implement core functionality
- Add styling and polish
1. **Test and Deploy** 🚀
\`\`\`sh
pnpm build
pnpm test
pnpm deploy --prod
\`\`\`
`.trim()} />
---
## Document: Slot
Use the low-level Slot component for advanced slot management in custom pages and MDX content.
URL: /docs/components/slot
# Slot
The `Slot` component provides low-level access to Zudoku's slot system, allowing you to
programmatically manage content injection in custom pages and React components.
## Import
```tsx
import { Slot } from "zudoku/components";
```
## Components
The Slot system consists of two main components:
### Slot.Target
Renders content that has been injected into a specific slot.
```tsx
No content} />
```
**Props:**
- `name` (required): The slot name to render content for
- `fallback` (optional): Content to show when no slot content is available
### Slot.Source
Injects content into a specific slot. This component renders nothing but registers content to be
displayed by `Slot.Target` components.
```tsx
Content to inject
```
**Props:**
- `name` (required): The slot name to inject content into
- `type` (optional): How to handle multiple content sources
- `"replace"` (default): Replace existing content
- `"prepend"`: Add before existing content
- `"append"`: Add after existing content
- `children`: The content to inject
## Usage Example
```tsx
function MyCustomPage() {
return (
My Page
{/* Render slot content here */}
{/* Inject content into a slot */}
Custom header content
Page content here...
);
}
```
## Type Safety
The Slot component is fully type-safe. All predefined slot names are available with autocomplete:
```tsx
// These will show autocomplete for available slot names
Content
```
### Adding Custom Slot Names
To add your own slot names with full type safety, augment the `CustomSlotNames` type:
```tsx
// types/slots.d.ts
declare module "zudoku" {
type CustomSlotNames = "my-custom-header" | "sidebar-extra";
}
```
## Integration with Configuration
The Slot component works with configuration-based slots. Content defined in your `zudoku.config.tsx`
and content injected via `Slot.Source` components will be combined according to the `type`
parameter.
```tsx
// In zudoku.config.tsx
slots: {
"page-header":
Config header
}
// In your component
// Renders both: Config header, then Component button
```
For basic configuration usage, see the [Slots Configuration](/docs/configuration/slots)
documentation.
---
## Document: Slider
URL: /docs/components/slider
# Slider
import { Slider } from "zudoku/ui/Slider";
import { Label } from "zudoku/ui/Label";
import { useState } from "react";
export const SliderWithValue = () => {
const [value, setValue] = useState([75]);
return (
{value[0]}%
);
};
A slider component built on Radix UI primitives for numeric input with a range.
## Import
```tsx
import { Slider } from "zudoku/ui/Slider";
```
## Basic Usage
```tsx
```
## Range Slider
```tsx
```
## With Label
```tsx
```
## Disabled State
```tsx
```
## Custom Step Size
```tsx
```
## With Value Display
```tsx {2,8,10}
const SliderWithValue = () => {
const [value, setValue] = useState([75]);
return (
{value[0]}%
);
};
```
## Features
- **Accessibility**: Full keyboard navigation and screen reader support
- **Multiple Thumbs**: Support for range sliders with multiple values
- **Customizable**: Easy to style with className prop
- **Touch Support**: Optimized for touch devices
---
## Document: Sidecar Box
URL: /docs/components/sidecar-box
# Sidecar Box
import * as SidecarBox from "zudoku/ui/SidecarBox";
import { SyntaxHighlight } from "zudoku/ui/SyntaxHighlight";
import { Badge } from "zudoku/ui/Badge";
A framed panel with an optional head, body, and footer. The OpenAPI plugin builds its sidecar with
it (the request body, response, and example boxes), so reaching for it in custom plugin pages or MDX
gives you content that matches that look.
## Import
```tsx
import * as SidecarBox from "zudoku/ui/SidecarBox";
import { SyntaxHighlight } from "zudoku/ui/SyntaxHighlight";
```
## Components
- `SidecarBox.Root` - The outer framed container
- `SidecarBox.Head` - Header row, typically a title or controls
- `SidecarBox.Body` - Main content area with its own inner border
- `SidecarBox.Footer` - Footer row for notes or actions
`Head`, `Body`, and `Footer` are all optional. Use just `Root` and `Body` for a plain framed panel.
## With a status badge
The head is a plain flex row, so `justify-between` aligns a title on the left and a status badge (or
any control) on the right, above an embedded code body.
GET /users/{`{id}`}200
```tsx
GET /users/{id}200
```
## Anatomy
All four parts together. The head and footer sit flush against the framed body in the middle.
ResponseA 200 response returns the user object.application/json
```tsx
ResponseA 200 response returns the user object.application/json
```
## With a code block
For code or JSON, drop the body padding with `className="p-0"` and let an embedded `SyntaxHighlight`
own the spacing. This is the pattern the OpenAPI plugin uses for its example boxes.
Example response200 OK
```tsx
Example response200 OK
```
## Props
Each part accepts `children` and an optional `className` to extend or override its styling.
| Component | Description |
| ------------------- | ------------------------------------------------------------------ |
| `SidecarBox.Root` | Outer frame. Sets the rounded border, background, and shadow. |
| `SidecarBox.Head` | Top row. A flex container, so layout utilities apply directly. |
| `SidecarBox.Body` | Content area with its own inner border. Use `p-0` for code blocks. |
| `SidecarBox.Footer` | Bottom row for notes or actions. |
---
## Document: /docs/components/shadcn
URL: /docs/components/shadcn
All shadcn/ui components are available for direct import from their respective UI modules:
export const components = [
"Accordion",
"Alert",
"AspectRatio",
"Badge",
"Breadcrumb",
"Button",
"Card",
"Carousel",
"Checkbox",
"Collapsible",
"Command",
"Dialog",
"Drawer",
"DropdownMenu",
"Form",
"HoverCard",
"Input",
"Label",
"Pagination",
"Popover",
"Progress",
"RadioGroup",
"ScrollArea",
"Select",
"Skeleton",
"Slider",
"Switch",
"Tabs",
"Textarea",
"Toggle",
"ToggleGroup",
"Tooltip",
];
# `shadcn/ui` components
We use a couple components from [shadcn/ui](https://ui.shadcn.com). To make them re-usable for and
use them in your project you can import them from `zudoku/ui/*`.
For example:
```tsx
import { Button } from "zudoku/ui/Button";
```
Following components are available:
{components.map((comp) => {
const link = comp.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
return (
---
## Document: Select
URL: /docs/components/select
# Select
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectSeparator,
SelectTrigger,
SelectValue,
} from "zudoku/ui/Select";
import { Label } from "zudoku/ui/Label";
A select component built on Radix UI primitives for choosing from a list of options.
## Import
```tsx
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "zudoku/ui/Select";
```
## Components
The Select component consists of several sub-components:
- `Select` - The main container (root)
- `SelectTrigger` - Button that opens the select menu
- `SelectValue` - Displays the selected value
- `SelectContent` - The dropdown content container
- `SelectItem` - Individual selectable option
- `SelectLabel` - Section labels within the dropdown
- `SelectSeparator` - Visual separator between groups
- `SelectGroup` - Groups related items together
## Basic Usage
```tsx
```
## With Labels and Groups
```tsx
```
## With Label
```tsx
```
## Features
- **Keyboard Navigation**: Full keyboard support for navigation and selection
- **Accessibility**: Proper ARIA attributes and screen reader support
- **Controlled/Uncontrolled**: Can be used with or without state management
- **Searchable**: Built-in search functionality when typing
- **Custom Positioning**: Flexible positioning options
---
## Document: Secret
Use the Secret component to display and manage API keys in your Zudoku application.
URL: /docs/components/secret
# Secret
import { Secret } from "zudoku/ui/Secret";
import { Value } from "zudoku/ui/Value";
The `Secret` component can be used to display secrets, typically API keys, in your Zudoku
application.
## Import
```tsx
import { Secret } from "zudoku/ui/Secret";
```
## Usage
```tsx
{
console.log("Copied secret:", secret);
}}
onReveal={(revealed) => {
console.log("Revealed:", revealed);
}}
/>
```
Client IDClient Secret
---
## Document: API Playground
URL: /docs/components/playground
# API Playground
You can use this component to allow users to test your API anywhere in your documentation.
This component is **only available in [MDX files](/docs/markdown/mdx)** and you can use it directly
without any imports:
```tsx
```
:::tip{title="Hot tip"}
The Playground can point to any API endpoint, even if it's not in the API catalog.
:::
## Props
```ts
type PlaygroundProps = {
server: string;
url: string;
method: string;
headers?: Header[];
pathParams?: PathParam[];
queryParams?: QueryParam[];
body?: string;
};
```
## Examples
### Zuplo Echo
In this example, we're using the Zuplo Echo API to test the `POST /hello-world/{name}` endpoint.
```tsx
```
### JSON Placeholder
In this example, we're using the JSON Placeholder API to test the `GET /photos` endpoint. We can
also change the Text on the button to something more specific to the API.
Test Photos Endpoint
```tsx
Test Photos Endpoint
```
---
## Document: Mermaid
Render Mermaid diagrams dynamically with client-side rendering.
URL: /docs/components/mermaid
# Mermaid
import { Mermaid } from "zudoku/mermaid";
The `Mermaid` component renders [Mermaid diagrams](https://mermaid.js.org/) in the browser using
client-side rendering.
:::info{title="Build-Time Alternative"}
For static diagrams, consider [build-time rendering with rehype-mermaid](/docs/guides/mermaid).
:::
## Prerequisites
Install the `mermaid` peer dependency:
```bash
npm install mermaid
```
## Import
```tsx
import { Mermaid } from "zudoku/mermaid";
```
## Props
```ts
type MermaidProps = {
chart: string; // Mermaid diagram definition
config?: MermaidConfig; // Optional Mermaid configuration
} & ComponentProps<"div">;
```
## Usage
### Basic Usage
```tsx
B;
A-->C;
B-->D;
C-->D;`}
/>
```
B;
A-->C;
B-->D;
C-->D;`}
/>
### With Configuration
```tsx
>Bob: Hello Bob
Bob-->>Alice: Hi Alice`}
/>
```
>Bob: Hello Bob
Bob-->>Alice: Hi Alice`}
/>
---
## Document: Markdown
URL: /docs/components/markdown
# Markdown
A component for rendering markdown content with syntax highlighting and custom components.
## Import
```tsx
import { Markdown } from "zudoku/components";
```
## Props
```ts
type MarkdownProps = {
content: string;
className?: string;
components?: Components;
};
```
## Usage
### Basic Markdown
```tsx
```
### With Custom Styling
```tsx
```
### With Custom Components
```tsx
const customComponents = {
h1: ({ children }) =>
{children}
,
p: ({ children }) =>
{children}
,
};
;
```
:::info
Custom components provided via the `components` prop will merge with default MDX components,
allowing you to override specific elements while keeping others intact.
:::
## Features
- **GitHub Flavored Markdown**: Full GFM support including tables, strikethrough, and task lists
- **Syntax Highlighting**: Code blocks with configurable Shiki themes
- **Raw HTML Support**: Safely renders HTML mixed with markdown
- **Custom Components**: Override default markdown elements with custom React components
- **Prose Styling**: Built-in typography styles with dark mode support
## Supported Markdown
### Headers
```markdown
# H1 Header
## H2 Header
### H3 Header
```
### Emphasis
```markdown
_italic_ or *italic*
**bold** or __bold__
**_bold italic_**
~~strikethrough~~
```
### Lists
```markdown
- Unordered list item
- Another item
- Nested item
1. Ordered list item
2. Another numbered item
```
### Code
The Markdown component supports both inline code and code blocks with syntax highlighting.
**Inline code** with backticks:
```markdown
Inline `code` with backticks
```
**Code blocks** with syntax highlighting:
````markdown
```javascript
// Code block with syntax highlighting
function hello() {
console.log("Hello, world!");
}
```
````
For advanced syntax highlighting features like line numbers, titles, and line highlighting, see:
- [Code Blocks](/docs/markdown/code-blocks) - Markdown code block syntax and features
- [Syntax Highlight](/docs/components/syntax-highlight) - React component for syntax highlighting
### Links and Images
```markdown
[Link text](https://example.com) 
```
### Tables
```markdown
| Column 1 | Column 2 |
| -------- | -------- |
| Cell 1 | Cell 2 |
| Cell 3 | Cell 4 |
```
## Configuration
The Markdown component automatically uses:
- **Remark GFM**: For GitHub Flavored Markdown features
- **Rehype Raw**: For HTML support
- **Shiki**: For syntax highlighting with your configured themes
:::tip
The Markdown component integrates with Zudoku's syntax highlighting configuration and will use the
same themes as configured in your Zudoku options.
:::
---
## Document: Link
URL: /docs/components/link
# Link
A navigation component that provides client-side routing capabilities for internal links and
external link handling.
## Import
```tsx
import { Link } from "zudoku/components";
```
## Usage
### Basic Internal Link
```tsx
Get Started
```
### Link with State
```tsx
Profile
```
### Replace History Entry
```tsx
Login
```
### External Link Behavior
```tsx
Full Page Reload
```
## Examples
### Navigation Menu
```tsx
function Navigation() {
return (
);
}
```
### Breadcrumb Navigation
```tsx
function Breadcrumbs({ path }) {
return (