# 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) ![Image alt text](image.jpg) ```
See link and image examples [Link text](https://example.com) ![Image alt text](https://images.unsplash.com/photo-1588083066783-8828e623bad7?q=75&w=400&auto=format&fit=crop)
### 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
Hello
; } ``` ```` Result: ```tsx {4-6} /react/ title="Example.tsx" showLineNumbers import { useEffect } from "react"; function Example() { useEffect(() => { console.log("Mounted"); }, []); return
Hello
; } ``` ## 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" colored foreground bold text dimmed text underlined text reversed text strikethrough text underlined + strikethrough text ``` Usage: ````md ```ansi title="Terminal Output" colored foreground bold text dimmed text underlined text reversed text strikethrough text underlined + strikethrough text ``` ```` 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 ![API Architecture](/images/diagram.png) ``` 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" ![API Architecture](./image.png) ``` --- ## 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. ![Inserting a doc before the first sidebar item](./navigation-rules/insert-doc.png) 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. ![Inserting a link after the last sidebar item](./navigation-rules/insert-doc-end.png) ## 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 }, } ``` ![Modifying a sidebar item's icon](./navigation-rules/modify-item.png) ## 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 ![API Architecture](/images/diagram.png) ``` ## 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 ![](./tweakcn0.webp) ![](./tweakcn1.webp) 1. Copy the registry URL from the "Copy" section ![](./tweakcn2.webp) 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 🚀 ![](./tweakcn3.webp) 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": () => ( ), } ``` ### Dynamic Content with Routing ```tsx slots: { "top-navigation-side": ({ location, navigate }) => (
), } ``` --- ## 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.

); ``` ## Layout ### Collapsible Sidebar The navigation sidebar is collapsible by default. A small toggle button on the sidebar's right border lets users hide and reveal it. Configure the behavior under `site.sidebar`: ```tsx title=zudoku.config.tsx { site: { sidebar: { collapsible: true, // default: true. Set to false to disable the toggle entirely. toggleVisibility: "always", // "always" (default) or "hover" — show button only when hovering the sidebar's right edge togglePosition: "bottom", // "top", "center", or "bottom" (default) }, } } ``` For finer vertical placement, override the `--sidebar-toggle-y` CSS variable in your stylesheet: ```css :root { --sidebar-toggle-y: 30%; } ``` The toggle button carries `aria-expanded="true"` when the sidebar is open and `"false"` when collapsed. Combine it with the `[data-sidebar-toggle]` selector to position the button differently per state: ```css [data-sidebar-toggle][aria-expanded="true"] { --sidebar-toggle-y: 20%; } [data-sidebar-toggle][aria-expanded="false"] { --sidebar-toggle-y: 80%; } ``` ### Banner Add a banner message to the top of the page: ```tsx title=zudoku.config.tsx { site: { banner: { message: "Welcome to our beta documentation!", color: "info", // "note" | "tip" | "info" | "caution" | "danger" or custom dismissible: true } } } ``` ### Footer The footer configuration has its own dedicated section. See the [Footer Configuration](./footer) for details. ## Complete Example Here's a comprehensive example showing all available page configuration options: ```tsx title=zudoku.config.tsx { site: { title: "My API Documentation", logo: { src: { light: "/images/logo-light.svg", dark: "/images/logo-dark.svg" }, alt: "Company Logo", width: "100px", }, notFoundPage: , banner: { message: "Welcome to our documentation!", color: "info", dismissible: true }, } } ``` --- ## Document: /docs/configuration/sentry URL: /docs/configuration/sentry # Sentry Sentry is a popular error tracking tool that helps you monitor and fix crashes in real time. It provides you with detailed error reports, so you can quickly identify and resolve issues before they affect your users. ## Enable Sentry 1. To enable it you have to first install the optional dependency `@sentry/react`. ```bash npm install --save @sentry/react ``` 2. And then set the `SENTRY_DSN` environment variable in your Zudoku project. ```bash SENTRY_DSN=https://your-sentry-dsn ``` ## Release management However this does not handle release management for you. For that you can [create a custom `vite.config.ts`](./vite-config) and use the `@sentry/vite-plugin` plugin. ```ts import { sentryVitePlugin } from "@sentry/vite-plugin"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [ sentryVitePlugin({ authToken: "your-token", org: "your-org", project: "your-project", }), ], }); ``` --- ## Document: Search Learn how to configure and customize search functionality in Zudoku, including setup instructions for Pagefind and Inkeep providers, result transformation, and ranking options. URL: /docs/configuration/search # Search Zudoku offers search functionality that enhances user experience by enabling content discovery across your site. When configured, a search bar will appear in the header, allowing users to quickly find relevant information on any page. We currently support three search providers: - [Pagefind](https://pagefind.app/) - [Algolia DocSearch](https://docsearch.algolia.com/) - [Inkeep](https://inkeep.com/) ## Pagefind [Pagefind](https://pagefind.app/) is a lightweight, static search library that can be used to add search to your Zudoku site without any external services. To enable pagefind search, configure the `search` option in your configuration: ```typescript { search: { type: "pagefind", // Optional: Maximum number of sub results per page maxSubResults: 3, // Optional: Configure search result ranking (defaults shown below) ranking: { termFrequency: 0.8, pageLength: 0.6, termSimilarity: 1.2, termSaturation: 1.2, }, } } ``` ### Transforming/Filtering Search Results You can transform or filter search results using the `transformResults` option. This function receives the search result along with the current auth state and context, allowing you to: - Filter results based on user permissions - Modify result content - Add custom results The type of `result` is the same as [the type returned by Pagefind's search API](https://github.com/Pagefind/pagefind/blob/03552d041d9533b09563f6c50466b25d394ece64/pagefind_web_js/types/index.d.ts#L123-L160). ```typescript { search: { type: "pagefind", transformResults: ({ result, auth, context }) => { // Return false to filter out the result if (!auth.isAuthenticated) return false; // Return true or undefined to keep the original result if (result.url.includes("/private/")) return true; // Return a modified result return { ...result, title: `${result.title} (${context.meta.title})` }; } } } ``` For more information about how Pagefind's ranking system works and how to customize it for your content, see the [Pagefind ranking documentation](https://pagefind.app/docs/ranking/). ## Algolia DocSearch [Algolia DocSearch](https://docsearch.algolia.com/) is a free search solution for open-source documentation sites. You can apply for the free DocSearch program or use your own Algolia account. :::note Algolia DocSearch is provided as a separate plugin package (`@zudoku/plugin-search-algolia`). We are experimenting with external plugin packages as a distribution model for new integrations. ::: ### Installation ```bash npm install @zudoku/plugin-search-algolia ``` ### Configuration Import the plugin and add it to the `plugins` array in your Zudoku configuration: ```typescript import { algoliaSearchPlugin } from "@zudoku/plugin-search-algolia"; const config = { plugins: [ algoliaSearchPlugin({ appId: "YOUR_APP_ID", apiKey: "YOUR_SEARCH_API_KEY", indices: ["YOUR_INDEX_NAME"], }), ], }; ``` You can get your credentials by [applying for DocSearch](https://docsearch.algolia.com/apply/) or creating an index through the [Algolia dashboard](https://www.algolia.com/dashboard). Any additional [DocSearch options](https://docsearch.algolia.com/docs/api/) can be passed alongside the required fields. ## Inkeep [Inkeep](https://inkeep.com/) is an AI-powered search and chat platform that can index your documentation and provide intelligent search capabilities to your users. ### Setting up Inkeep Integration Before you can use Inkeep search in your Zudoku site, you need to set up an Inkeep integration and have your site indexed. Here's how to get started: #### 1. Create an Inkeep Account 1. Go to [Inkeep](https://inkeep.com/) and sign up for an account 2. Navigate to the [Inkeep Portal](https://portal.inkeep.com/) #### 2. Set up Site Indexing 1. In the Inkeep Portal, create a new project or integration 2. Configure your site URL so Inkeep can crawl and index your documentation 3. Ensure your Zudoku site is deployed and publicly accessible for indexing 4. Wait for Inkeep to crawl and index your site content (this may take some time) #### 3. Get Your Integration Credentials To add Inkeep search to your site you will need to copy some variables from your [Inkeep account setting](https://portal.inkeep.com/): - API Key - Integration ID - Organization ID #### 4. Configure Zudoku Once you have your credentials and your site is indexed, you can configure the `search` option in your [Zudoku Configuration](./overview.md): ```typescript { // ... search: { type: "inkeep", apiKey: "", integrationId: "", organizationId: "", primaryBrandColor: "#26D6FF", organizationDisplayName: "Your Organization Name", } // ... } ``` ### Customizing Inkeep Any other [base settings](https://docs.inkeep.com/cloud/ui-components/common-settings/base) can be set alongside the required fields, for example `filters` to scope results or `transformSource` to customize how sources are displayed. You can also pass [`searchSettings`](https://docs.inkeep.com/cloud/ui-components/common-settings/search), [`aiChatSettings`](https://docs.inkeep.com/cloud/ui-components/common-settings/ai-chat), and `modalSettings` to customize the respective parts of the search experience. They are merged with Zudoku's defaults and passed through to Inkeep as-is, so any option Inkeep supports can be used — including ones added after this Zudoku version was released. For example, to categorize results into tabs based on their URL: ```typescript { // ... search: { type: "inkeep", // ...required fields from above transformSource: (source) => { if (!source.url.includes("/blog/")) return source; return { ...source, tabs: [...(source.tabs ?? []), "Blog"] }; }, searchSettings: { tabs: [["All", { isAlwaysVisible: true }], "Blog"], }, aiChatSettings: { aiAssistantName: "My Assistant", }, }, // ... } ``` --- ## Document: Protected Routes Learn how to protect specific routes in your documentation using authentication, including simple array patterns, advanced authorization with reason codes, and custom callback functions. URL: /docs/configuration/protected-routes # Protected Routes You can protect specific routes in your documentation by adding the `protectedRoutes` property to your [Zudoku configuration](./overview.md). This requires [authentication](./authentication.md) to be configured. The property supports two formats: a simple array of path patterns, or an advanced object format with custom authorization logic. :::note{title="SSR vs SSG protection"} In **SSR mode**, `protectedRoutes` is enforced both client-side (login dialog) and at the bundle level. Chunks containing content for protected routes are isolated into a separate, auth-gated directory and never served to unauthenticated clients. See the [Server-side Content Protection guide](../guides/server-side-content-protection.md) for the full mechanics and caveats. In **SSG mode** there is no server, so `protectedRoutes` is client-side only. The JavaScript chunks for protected routes are still fetchable by anyone who knows the URL. Don't rely on SSG `protectedRoutes` to hide sensitive information. ::: ## Array Format The simplest way to protect routes is to provide an array of path patterns. Users must be authenticated to access these routes. ```typescript title="zudoku.config.ts" { // ... protectedRoutes: [ "/admin/*", // Protect all routes under /admin "/settings", // Protect the settings page "/api/*", // Protect all API-related routes "/private/:id" // Protect dynamic routes with parameters ], // ... } ``` When a user tries to access a protected route, a login dialog will appear prompting them to sign in or register. After logging in, they are automatically redirected back to the route they were trying to access. ## Object Format For more complex authorization logic, you can provide a record mapping route patterns to custom callback functions: ```typescript title="zudoku.config.ts" { // ... protectedRoutes: { // Only allow authenticated users with admin role "/admin/*": ({ auth }) => auth.isAuthenticated && auth.user?.role === "admin", // Check if user has enterprise access "/api/enterprise/*": ({ auth }) => auth.isAuthenticated && auth.user?.subscription === "enterprise", // Allow access to beta features based on user attributes "/beta/*": ({ auth }) => auth.isAuthenticated && auth.user?.betaAccess === true, }, // ... } ``` ### Callback Parameters The callback function receives an object with: - `auth` - The current authentication state, including `isAuthenticated`, `isPending`, `profile`, and more - `context` - The Zudoku context providing access to configuration and utilities - `reasonCode` - An object containing the reason code constants `UNAUTHORIZED` and `FORBIDDEN` (see [Reason Codes](#reason-codes)) ### Return Values The callback can return a `boolean` or a reason code string: | Return value | Behavior | | ------------------------- | ------------------------------------------------- | | `true` | Allow access to the route | | `false` | Treated as `UNAUTHORIZED` - prompts login | | `reasonCode.UNAUTHORIZED` | Show a login dialog prompting the user to sign in | | `reasonCode.FORBIDDEN` | Show a 403 "Access Denied" page | ## Reason Codes Reason codes allow you to distinguish between users who need to sign in and users who are signed in but lack permission. This is useful for building role-based or attribute-based access control. - **`UNAUTHORIZED`** - The user is not authenticated. A login dialog is shown, and navigation to the route is blocked until the user signs in. - **`FORBIDDEN`** - The user is authenticated but does not have permission. A 403 "Access Denied" page is displayed instead of the route content. ```typescript title="zudoku.config.ts" { // ... protectedRoutes: { // Members-only page: unauthenticated users see a login prompt "/only-members": ({ auth, reasonCode }) => auth.isAuthenticated ? true : reasonCode.UNAUTHORIZED, // VIP page: unauthenticated users see a login prompt, // authenticated users without permission see "Access Denied" "/vip-lounge": ({ auth, reasonCode }) => !auth.isAuthenticated ? reasonCode.UNAUTHORIZED : auth.profile?.email?.endsWith("@example.com") ? true : reasonCode.FORBIDDEN, }, // ... } ``` ## Navigation Blocking When a user navigates to a route that returns `false` or `UNAUTHORIZED`, navigation is intercepted before the page changes. The user stays on the current page while a login dialog is displayed. If the user cancels, they remain on the current page. If they log in successfully, navigation automatically proceeds to the protected route. Routes that return `FORBIDDEN` do not block navigation — the user navigates to the route and sees the "Access Denied" page. ## Path Patterns The path patterns follow the same syntax as [React Router](https://reactrouter.com): - `:param` matches a URL segment up to the next `/`, `?`, or `#` - `*` matches zero or more characters up to the next `/`, `?`, or `#` - `/*` matches all characters after the pattern For example: - `/users/:id` matches `/users/123` or `/users/abc` - `/docs/*` matches `/docs/getting-started` or `/docs/api/reference` - `/settings` matches only the exact path `/settings` ## Server-side Protection (SSR mode) In SSR mode, Zudoku additionally isolates the JavaScript chunks for protected routes into an auth-gated directory that unauthenticated users cannot fetch. This covers content sources that the build can statically analyze (MDX docs, file-based OpenAPI, user custom pages with `lazy` imports) and has caveats for dynamically-generated routes and inline content. See the [Server-side Content Protection guide](../guides/server-side-content-protection.md) for the full explanation, auto-detection rules, caveats, and pre-ship checklist. ## Next Steps - Learn about [authentication providers](./authentication.md#authentication-providers) supported by Zudoku - Configure [user data](./authentication.md#user-data) display - Read the [Server-side Content Protection guide](../guides/server-side-content-protection.md) if you're deploying with an SSR adapter --- ## Document: Configuration File Learn how to configure your Zudoku documentation site using the configuration file. Covers file formats, options, examples, and best practices. URL: /docs/configuration/overview # Configuration File Zudoku uses a single file for configuration. It controls the structure, metadata, style, plugins, and routing for your documentation. You can find the file in the root directory of your project. It will start with `zudoku.config`. The file can be in either JavaScript or TypeScript format and use a `.js`, `.mjs`, `.jsx`, `.ts`, or `.tsx` file extension: - `zudoku.config.ts` - `zudoku.config.tsx` - `zudoku.config.js` - `zudoku.config.jsx` - `zudoku.config.mjs` When you create a project, a default configuration file is generated for you. This file is a good starting point and can be customized to suit your needs. :::note{title="Security Consideration"} The Zudoku configuration file runs on both client and server at runtime. Avoid including secrets directly in your config as they may be exposed to the client. ::: ## Example Below is an example of the default Zudoku configuration. You can edit this configuration to suit your own needs. ```ts title=zudoku.config.ts import type { ZudokuConfig } from "zudoku"; const config: ZudokuConfig = { navigation: [ { type: "category", label: "Documentation", items: ["introduction", "example"], }, { type: "link", to: "api", label: "API Reference" }, ], redirects: [{ from: "/", to: "/docs/introduction" }], apis: { type: "file", input: "./apis/openapi.yaml", path: "/api", }, docs: { files: "/pages/**/*.{md,mdx}", }, }; export default config; ``` ## Configuration options ### `apis` There are multiple options for referencing your OpenAPI document. The example below uses a URL to an OpenAPI document, but you can also use a local file path. For full details on the options available, see the [API Reference](./api-reference.md). ```ts { // ... "apis": { "type": "url", "input": "https://rickandmorty.zuplo.io/openapi.json", "path": "/api" } // ... } ``` ### `site` Controls global page attributes across the site, including logos and the site title. **Example:** ```ts { // ... "site": { "title": "Our Documentation", "logo": { "src": { "light": "/logos/zudoku-light.svg", "dark": "/logos/zudoku-dark.svg" }, "width": "99px" } } // ... } ``` ### `navigation` Defines navigation for both the top bar and the sidebar. Items can be categories, links or custom pages. ```ts { // ... "navigation": [ { "type": "category", "label": "Docs", "items": ["introduction"] }, { "type": "link", "to": "api", "label": "API Reference" } ] // ... } ``` ### `theme` Allows you to control the dark and light themes that persist across each MDX page, and the API reference. You can customize your theme as much as you want using [ShadCDN UI theme variables](https://ui.shadcn.com/docs/theming#list-of-variables). In the example below only the `primary` and `primaryForeground` variables are used but you can add any additional variables from ShadCDN UI that you would like to change. **Tip**: Use the [ShadCDN UI Theme Generator](https://zippystarter.com/tools/shadcn-ui-theme-generator) to create a great looking theme based off your primary color. **Example:** ```ts { // ... "theme": { "light": { "primary": "316 100% 50%", "primaryForeground": "360 100% 100%" }, "dark": { "primary": "316 100% 50%", "primaryForeground": "360 100% 100%" } } // ... } ``` ### `metadata` Controls the site metadata for your documentation. All possible options are outlined in the example below. **Example:** ```ts { // ... "metadata": { "title": "Example Website Title", "description": "This is an example description for the website.", "logo": "https://example.com/logo.png", "favicon": "https://example.com/favicon.ico", "generator": "Website Generator 1.0", "applicationName": "Example App", "referrer": "no-referrer", "keywords": ["example", "website", "metadata", "SEO"], "authors": ["John Doe", "Jane Smith"], "creator": "John Doe", "publisher": "Example Publisher Inc." } // ... } ``` ### `header` Configures the header navigation and placement of header elements (navigation, search, auth). ```ts { header: { navigation: [ { label: "Docs", id: "docs" }, { label: "API", id: "api" }, ], placements: { navigation: "start", // "start" | "center" | "end" search: "end", // "start" | "center" | "end" auth: "end", // "start" | "center" | "end" | "navigation" }, themeSwitcher: { enabled: false, // optional, defaults to true }, } } ``` Use `header.themeSwitcher.enabled: false` to hide the light/dark theme switch from the desktop header and mobile navigation drawer. ### `defaults` Sets global default options for APIs that apply to all API configurations. Individual API options will override these defaults when specified. ```ts { defaults: { apis: { examplesLanguage: "shell", disablePlayground: false, showVersionSelect: "if-available", }, } } ``` ### `docs` Configures where your non API reference documentation can be found in your folder structure. The default is shown in the example below and you don't need to change it unless you want a different structure in place, or to have it match an existing structure that you already have. **Example:** ```ts { // ... "docs": { "files": "/pages/**/*.{md,mdx}" } // ... } ``` ### `sitemap` Controls the sitemap for your documentation. All possible options are outlined in the example below. ```ts { // ... "sitemap": { // The base url for your site // Required "siteUrl": "https://example.com", // The change frequency for the pages // Defaults to daily "changefreq": "daily", // The priority for the pages // Defaults to 0.7 "priority": 0.7, // The output directory for the sitemap // Defaults to undefined "outDir": "sitemaps/", // Whether to include the last modified date // Defaults to true "autoLastmod": true, // The pages to exclude from the sitemap // Can also be a function that returns an array of paths // () => Promise "exclude": ["/404", "/private/page"] } // ... } ``` ### `redirects` Implements any page redirects you want to use. This gives you control over the resource names in the URL. **Example:** ```ts { // ... "redirects": [ { "from": "/", "to": "/documentation/introduction" }, { "from": "/documentation", "to": "/documentation/introduction" } ] // ... } ``` ### `port` The port on which the development server will run. Defaults to `3000`. This option can also be passed to the CLI as `--port' (which takes precedence). ```ts { "port": 9001 } ``` If the port is already in use, the next available port will be used. ### `basePath` Sets the base path for your documentation site. This is useful when you want to host your documentation under a specific path. ```ts { basePath: "/docs", // A page defined as `/intro` would result in: https://example.com/docs/intro } ``` ### `canonicalUrlOrigin` Sets the canonical [origin URL](https://developer.mozilla.org/en-US/docs/Web/API/URL/origin) for your documentation site. This is used for SEO purposes and helps search engines understand the preferred version of a page. ```ts { basePath: '/docs', canonicalUrlOrigin: "https://example.com", // visiting the page `/intro` would result in: // https://example.com/docs/intro } ``` This is the resulting HTML that will be added to the `` of your pages: ```html ``` ### `cdnUrl` Configures the CDN URL for your documentation site's assets. You can provide either a string for a single CDN URL or an object to specify different URLs for base and media assets. ```ts // Single CDN URL { cdnUrl: "https://cdn.example.com" } // Separate URLs for base and media assets { cdnUrl: { base: "https://cdn.example.com", media: "https://media.example.com" } } ``` ### `https` Enables HTTPS for the dev server. `key` and `cert` are required and `ca` is optional. ```ts { "https": { "key": "/path/to/key.pem", "cert": "/path/to/cert.pem", "ca": "/path/to/ca.pem" } } ``` ### `enableStatusPages` Enables static generation of status pages for your site. This results in several files (404.html, 500.html, etc.) being generated in the `dist` directory. This is useful as many hosting providers will serve these files automatically when a user visits a non-existent page or encounters an error. This option is enabled by default, but you can disable it if you don't need these pages. ```ts { enableStatusPages: false; } ``` ## Multiple Files The configuration file is a standard JavaScript or TypeScript file, so you can split it into multiple files if you prefer. This can be useful if you have a large configuration or want to keep your code organized. For example, if you wanted to move your navigation configuration to a separate file, you could create a new file called `navigation.ts` and export the navigation configuration from there. ```ts // navigation.ts import type { Navigation } from "zudoku"; export const navigation: Navigation = [ { type: "category", label: "Documentation", items: ["example", "other-example"], }, ]; ``` Then you can import the navigation configuration into your main configuration file. ```ts title=zudoku.config.ts // zudoku.config.ts import type { ZudokuConfig } from "zudoku"; import { navigation } from "./navigation"; const config = { // ... navigation, // ... }; export default config; ``` --- ## Document: OAuth Security Schemes URL: /docs/configuration/oauth-security-schemes # OAuth Security Schemes If your OpenAPI specification defines OAuth2 or OpenID Connect security schemes, Zudoku will display them as informational badges on your API operations. However, interactive OAuth flows (such as Authorization Code or Client Credentials) are **not active by default** in the playground. To enable OAuth-based authentication for API requests, you need to configure a Zudoku [authentication provider](./authentication.md). ## Why Zudoku Uses Its Own Authentication System OpenAPI `securitySchemes` describe _what_ authentication an API requires, but they don't include the runtime configuration needed to actually perform an OAuth flow — such as the client ID, client secret, redirect URI, or provider-specific settings. Rather than attempting to derive a working OAuth flow from incomplete spec metadata, Zudoku gives you full control through its authentication plugin system. This approach: - Works reliably across OAuth providers (Auth0, Azure B2C, Okta, Keycloak, etc.) without provider-specific workarounds - Avoids exposing client secrets in the browser - Handles token refresh, session management, and logout correctly - Integrates with Zudoku's [protected routes](./protected-routes.md) and [API identity](../concepts/auth-provider-api-identities.md) system ## Setting Up OAuth for the Playground To enable OAuth-based authentication in the API playground, follow these two steps: ### 1. Configure an Authentication Provider Add an `authentication` block to your `zudoku.config.ts`. This handles the OAuth flow (redirects, token exchange, session management) for your documentation portal. For example, using Auth0: ```ts title=zudoku.config.ts const config = { authentication: { type: "auth0", domain: "yourdomain.us.auth0.com", clientId: "", }, }; ``` Or using any OpenID Connect provider: ```ts title=zudoku.config.ts const config = { authentication: { type: "openid", clientId: "", issuer: "https://your-idp.example.com", }, }; ``` See [Authentication](./authentication.md) for the full list of supported providers. ### 2. Create an API Identity Plugin The authentication provider signs users into the portal. To use their token for API requests in the playground, create an [API Identity plugin](../concepts/auth-provider-api-identities.md) that bridges the two: ```ts title=zudoku.config.ts import { createApiIdentityPlugin } from "zudoku/plugins"; const config = { authentication: { type: "openid", clientId: "", issuer: "https://your-idp.example.com", }, plugins: [ createApiIdentityPlugin({ getIdentities: async (context) => [ { id: "oauth-token", label: "OAuth Token", authorizeRequest: (request) => { return context.authentication?.signRequest(request); }, }, ], }), ], }; ``` Once configured, users can sign in to your documentation portal and their OAuth token will be automatically attached to API requests made from the playground. ## What About Non-OAuth Security Schemes? Simple security schemes like API keys and HTTP bearer tokens defined in your OpenAPI spec work in the playground without any additional Zudoku configuration. Users can enter their credentials directly in the playground's Authorize dialog. OAuth2 and OpenID Connect schemes are the exception — they require the authentication provider configuration described above because performing OAuth flows requires runtime configuration that goes beyond what OpenAPI specifies. ## Disabling Security Scheme Display If you don't want security scheme information displayed at all (badges on operations, the security schemes section on the info page, and the Authorize dialog), you can disable it: ```ts title=zudoku.config.ts const config = { apis: { type: "file", input: "./openapi.json", path: "/api", options: { disableSecurity: true, }, }, }; ``` --- ## Document: Navigation Learn how to configure the navigation in Zudoku, including links, categories, documents, and custom pages. Understand the structure of the navigation array and how to use icons, labels, and paths. URL: /docs/configuration/navigation # Navigation import { Book, Code, FileText } from "zudoku/icons"; Zudoku uses a single `navigation` array to control both the top navigation tabs and the sidebar. Items at the root of this array appear as tabs, and nested items build the sidebar tree. Navigation entries can be links, document references, categories, custom pages, separators, sections, or filters. ## Basic configuration The navigation is defined using the `navigation` array in the Zudoku config file. Each item can be one of several types. At the simplest level you may only have links and categories. ```tsx title="zudoku.config.tsx" { "navigation": [ { "type": "category", "label": "Documentation", "icon": "book", "items": [ { "type": "doc", "file": "documentation/introduction", "label": "Introduction", "icon": "file-text" }, { "type": "doc", "file": "documentation/getting-started", "path": "/docs/quick-start", "label": "Quick Start" } ] }, { "type": "link", "to": "/api", "label": "API Reference", "icon": "code", "badge": { "label": "v2.0", "color": "blue" }, "display": "always" } ] } ``` ## Navigation Items Navigation items can be of these types: `category`, `doc`, `link`, `custom-page`, `separator`, `section`, or `filter`. - `link`: A direct link to a page or external URL. - `category`: A group of links that can be expanded or collapsed. - `doc`: A reference to a document by its file path: `file`. - `custom-page`: A custom page that is made of a React component, see [Custom Pages](../guides/custom-pages.md) - `separator`: A horizontal line to visually divide sidebar items. - `section`: A non-interactive heading label to group sidebar items. - `filter`: An inline search input that filters navigation items. Multiple filter inputs share the same search query. ### `type: link` `link` is the most basic item, it directly links to a path or URL. Use this for external resources or standalone pages. ```json { "type": "link", "label": "Support", "to": "/my/api" // or: https://example.com/my-external-link } ```
**TypeScript type declaration** ```ts type NavigationLink = { type: "link"; to: string; label: string; icon?: string; // Lucide icon name target?: "_self" | "_blank"; stack?: boolean; // open the destination as a stacked sub-nav badge?: { label: string; color: "green" | "blue" | "yellow" | "red" | "purple" | "indigo" | "gray" | "outline"; invert?: boolean; }; display?: | "auth" | "anon" | "always" | "hide" | ((params: { context: ZudokuContext; auth: UseAuthReturn }) => boolean); }; ```
### `type: category` The `category` type groups related items under a collapsible section. The `label` is the displayed text, and the `items` array can contain any navigation item type (documents, links, categories, custom pages, separators, sections, and filters). ```json { "type": "category", "label": "Getting Started", "collapsible": true, // optional "collapsed": false, // optional "items": [ { "type": "link", "label": "Support", "to": "https://support.example.com" } ] } ```
**TypeScript type declaration** ```ts type NavigationCategory = { type: "category"; icon?: string; // Lucide icon name items: Array; // any navigation item type, including string shorthands for docs label: string; collapsible?: boolean; collapsed?: boolean; stack?: boolean; // open the category's items as a stacked sub-nav link?: | string | { type: "doc"; file: string; label?: string; path?: string } | { type: "link"; to: string; label?: string }; display?: | "auth" | "anon" | "always" | "hide" | ((params: { context: ZudokuContext; auth: UseAuthReturn }) => boolean); }; ```
#### Category links A category can have a `link` property that makes the category label itself clickable, navigating to a document, a path, or an external URL. This is useful when you want a category that acts as both a group and a landing page. The `link` can be a simple string pointing to a file path, a `doc` object for more control, or a `link` object that points the category label at an arbitrary path or external URL: ```tsx title="String shorthand" { type: "category", label: "Configuration", link: "docs/configuration/overview", items: [ "docs/configuration/navigation", "docs/configuration/site", ], } ``` ```tsx title="Object form with custom path" { type: "category", label: "Documentation", link: { type: "doc", file: "home.md", path: "/", }, items: [ "guides/getting-started", "guides/advanced", ], } ``` The `doc` object form supports these properties: | Property | Type | Description | | -------- | -------- | -------------------------------------------------------- | | `type` | `"doc"` | Must be `"doc"` | | `file` | `string` | Path to the markdown file | | `label` | `string` | Override the label (defaults to the document title) | | `path` | `string` | Custom URL path (overrides the default file-based route) | ```tsx title="Link form pointing to a path or external URL" { type: "category", label: "API Reference", link: { type: "link", to: "/api", }, items: [ "guides/authentication", "guides/rate-limits", ], } ``` The `link` object form supports these properties: | Property | Type | Description | | -------- | -------- | ----------------------------------- | | `type` | `"link"` | Must be `"link"` | | `to` | `string` | Path or external URL to navigate to | | `label` | `string` | Override the category label | ### `type: doc` Doc is used to reference markdown files. The `label` is the text that will be displayed, and the `file` is the file path associated with a markdown file. ```json { "type": "doc", "label": "Overview", "file": "docs/overview" } ```
**TypeScript type declaration** ```ts type NavigationDoc = { type: "doc"; file: string; path?: string; icon?: string; label?: string; badge?: { label: string; color: "green" | "blue" | "yellow" | "red" | "purple" | "indigo" | "gray" | "outline"; invert?: boolean; }; display?: | "auth" | "anon" | "always" | "hide" | ((params: { context: ZudokuContext; auth: UseAuthReturn }) => boolean); }; ```
#### Using shorthands Documents can be referenced as strings (using their file path), which is equivalent to `{ "type": "doc", "file": "path" }`: ```json { "navigation": [ { "type": "category", "label": "Documentation", "icon": "book", "items": [ "documentation/introduction", "documentation/getting-started", "documentation/installation" ] }, { "type": "link", "to": "/api", "label": "API Reference", "icon": "code" } ] } ``` This is much more concise when you don't need custom labels, icons, or other properties for individual documents. Learn more in the [Markdown documentation](/docs/markdown/overview) #### Custom paths The `path` property allows you to customize the URL path for a document. By default, Zudoku uses the file path to generate the URL, but you can override this behavior by specifying a custom path. ```tsx title="Serving a doc at the root URL" { type: "doc", file: "home.md", path: "/", label: "Home", } ``` ```tsx title="Custom slug" { type: "doc", file: "guides/getting-started.md", path: "/start-here", label: "Start Here", } ``` When a file has a custom path, it will only be accessible at that custom path, not at its original file-based path. See [Documentation - Custom Paths](/docs/configuration/docs#custom-paths) for more details. :::note Avoid naming files `index.md` or `index.mdx` and relying on their default path. Some hosting providers (e.g. Vercel) automatically strip `/index` from URLs with a redirect, which can cause routing issues. Instead, give files descriptive names and use the `path` property to serve them at the desired URL. ::: ### `type: custom-page` Custom pages allow you to create standalone pages that are not tied to a Markdown document. This is useful for creating landing pages, dashboards, or any other custom content. ```tsx { type: "custom-page", path: "/a-custom-page", element: , display: "always" } ```
**TypeScript type declaration** ```ts type NavigationCustomPage = { type: "custom-page"; path: string; label?: string; element: any; icon?: string; // Lucide icon name layout?: "default" | "none"; badge?: { label: string; color: "green" | "blue" | "yellow" | "red" | "purple" | "indigo" | "gray" | "outline"; invert?: boolean; }; display?: | "auth" | "anon" | "always" | "hide" | ((params: { context: ZudokuContext; auth: UseAuthReturn }) => boolean); }; ```
Set `layout: "none"` to render the page without the default Zudoku layout (header, sidebar, footer). This is useful for fully custom landing pages. ### `type: separator` A visual divider line in the sidebar. Use separators to create visual breaks between groups of items. ```tsx { type: "category", label: "Documentation", items: [ "guides/getting-started", "guides/installation", { type: "separator" }, "guides/advanced", "guides/troubleshooting", ], } ```
**TypeScript type declaration** ```ts type NavigationSeparator = { type: "separator"; display?: | "auth" | "anon" | "always" | "hide" | ((params: { context: ZudokuContext; auth: UseAuthReturn }) => boolean); }; ```
### `type: section` A non-interactive heading label in the sidebar. Sections are rendered as small uppercase text and are useful for labeling groups of items without adding a collapsible wrapper. ```tsx { type: "category", label: "Documentation", items: [ { type: "section", label: "Getting Started" }, "guides/quickstart", "guides/installation", { type: "section", label: "Advanced" }, "guides/plugins", "guides/deployment", ], } ```
**TypeScript type declaration** ```ts type NavigationSection = { type: "section"; label: string; display?: | "auth" | "anon" | "always" | "hide" | ((params: { context: ZudokuContext; auth: UseAuthReturn }) => boolean); }; ```
### `type: filter` An inline search input that updates the shared navigation filter. When the user types, the query is applied across the sidebar so that only matching items remain visible. ```tsx { type: "category", label: "Documentation", items: [ { type: "filter", placeholder: "Filter documentation" }, "guides/getting-started", "guides/installation", "guides/authentication", "guides/deployment", ], } ```
**TypeScript type declaration** ```ts type NavigationFilter = { type: "filter"; placeholder?: string; display?: | "auth" | "anon" | "always" | "hide" | ((params: { context: ZudokuContext; auth: UseAuthReturn }) => boolean); }; ```
## Stacked navigation By default a category expands inline when clicked. Set `stack: true` and it opens as a full panel that slides in over the current menu, with a **Back** button to return. Use it for deep sections that would otherwise crowd the sidebar. `stack` works on categories and links. The panel content comes from the item itself: - A **category** with `stack: true` shows its own `items`. - A **link** with `stack: true` opens the navigation at its destination, such as the sidebar a plugin generates at that path. ```tsx title="Stack a category's items" { type: "category", label: "Shipping Guides", stack: true, items: ["guides/interstellar", "guides/intergalactic"], } ``` ```tsx title="Stack a linked section (e.g. a GraphQL plugin)" { type: "link", label: "Developer API", to: "/graphql/dev", stack: true, } ``` The **Back** button points to the section the stacked item belongs to (for example "Back to Our APIs"), so the page still works when someone opens it directly from a search result or a shared link. :::note A stacked link drills into wherever its `to` points, so the destination should be a section with its own navigation, such as a plugin route or a category landing page. The slide animation respects the user's reduced-motion setting and falls back to an instant swap. ::: ### Stacking generated navigation Plugin-generated sidebars (e.g. OpenAPI) can be stacked too, with a [navigation rule](#navigation-rules). This is useful for large APIs: instead of one long scroll, make each tag drill into its own panel. ```tsx title="Stack an OpenAPI tag" navigationRules: [ { type: "modify", match: "Shipments/Shipment/Shipment Management", set: { stack: true }, }, ], ``` The `match` targets a generated category by label (here, the `Shipment Management` tag under the `Shipments` API). See [Navigation Rules](#navigation-rules) for the matching syntax. ## Display Control All navigation items support a `display` property that controls when the item should be visible: - `"always"` (default): Always visible - `"auth"`: Only visible when user is authenticated - `"anon"`: Only visible when user is not authenticated - `"hide"`: Never visible (useful for temporarily hiding items) - **callback function**: For custom logic, pass a function that receives `{ context, auth }` and returns a boolean ```json { "type": "link", "label": "Admin Panel", "to": "/admin", "display": "auth" } ``` ### Custom Display Logic For more complex visibility rules, use a callback function: ```tsx { type: "link", label: "Premium Features", to: "/premium", display: ({ auth }) => { // Show only for users with premium role return auth.user?.role === "premium"; } } ``` The callback receives: - `context`: The `ZudokuContext` instance - `auth`: The authentication state from `UseAuthReturn`, including `user`, `isAuthenticated`, etc. ## Badges Navigation items can display badges with labels and colors. Badges support an optional `invert` property for styling: ```json { "type": "doc", "file": "api/v2", "badge": { "label": "Beta", "color": "yellow" } } ``` ## Icons Icons can be added to categories and documents by specifying an `icon` property. The value should be the name of a [Lucide icon](https://lucide.dev/icons) (e.g., `book` , `code` , `file-text` ). ```json { "type": "category", "label": "Getting Started", "icon": "book" } ``` They can also be set on individual documents in their front matter: ```md --- title: My Document sidebar_icon: book --- ``` ## Title & Labels All navigation items can have a `label` property that determines the displayed text. For `doc` items, the `label` is optional; if omitted, Zudoku uses the document's `title` from its front matter or the first `#` header. To override the navigation label without changing the document's `title`, use the `sidebar_label` property in the front matter: ```md --- title: My Long Title sidebar_label: Short Title --- ``` In this example, the document's title remains "My Long Title," but the sidebar displays "Short Title." For the complete list of supported frontmatter properties, see [Frontmatter](/docs/markdown/frontmatter). ## Common Patterns ### Serving a document at the root URL To make a markdown document accessible at `/`, use the `path` property to override the default file-based route: ```tsx title="Standalone root doc" navigation: [ { type: "doc", file: "home.md", path: "/", label: "Home", }, ], ``` ```tsx title="Category with root landing page" navigation: [ { type: "category", label: "Documentation", link: { type: "doc", file: "home.md", path: "/", }, items: [ "guides/getting-started", "guides/installation", ], }, ], ``` ### Landing page with hidden tab Use a `custom-page` with `display: "hide"` and `layout: "none"` to create a full-page landing experience that doesn't appear in the navigation tabs: ```tsx navigation: [ { type: "custom-page", path: "/", display: "hide", layout: "none", element: , }, { type: "category", label: "Documentation", items: ["docs/quickstart", "docs/installation"], }, ], ``` ### Organized sidebar with sections and separators Combine `section`, `separator`, and `filter` items to create a well-structured sidebar: ```tsx navigation: [ { type: "category", label: "Documentation", items: [ { type: "filter", placeholder: "Filter documentation" }, { type: "section", label: "Getting Started" }, "guides/quickstart", "guides/installation", { type: "separator" }, { type: "section", label: "Advanced" }, { type: "category", label: "Plugins", icon: "blocks", items: ["plugins/overview", "plugins/custom"], }, ], }, ], ``` ## Navigation Rules Plugins generate sidebar navigation automatically (e.g. from OpenAPI tags). Navigation rules let you customize that generated sidebar by inserting, modifying, sorting, moving, or removing items. To change the top-level tabs themselves, use the `navigation` array directly. ```tsx navigationRules: [ { type: "sort", match: "Shipments", by: (a, b) => a.label.localeCompare(b.label), }, ], ``` For a practical walkthrough with more examples, see the [Navigation Rules](/docs/guides/navigation-rules) guide. ### Rule Types Each rule has a `type` and a `match` property that targets a navigation item. | Type | Description | | -------- | ---------------------------------------------------------------------------------- | | `insert` | Add items `before` or `after` a matched item | | `modify` | Change `label`, `icon`, `badge`, `collapsed`, `collapsible`, `display`, or `stack` | | `remove` | Remove a matched item from the sidebar | | `sort` | Sort children of a matched category using a custom comparator | | `move` | Relocate a matched item `before` or `after` another item | ### Match Syntax The `match` property uses slash-separated segments to target items. The first segment identifies the top-level tab by label, and remaining segments navigate into the sidebar tree. Label matching is case-insensitive. ```json match: "Users/Get User"; // by label match: "Users/0"; // first item match: "Users/-1"; // last item match: "Users/Advanced/0"; // mixed nesting ``` --- ## Document: LLM-Friendly Documentation Export URL: /docs/configuration/llms # LLM-Friendly Documentation Export Zudoku can generate LLM-friendly versions of your documentation following the [llms.txt](https://llmstxt.org/) specification. During build, you can optionally generate: - **`.md` files** - Individual pages with frontmatter removed (via `publishMarkdown`) - **`llms.txt`** - Summary with links to all pages - **`llms-full.txt`** - Complete documentation in one file All options are disabled by default. ## Configuration LLM features are configured through the `docs` section in your config: ```tsx title="zudoku.config.tsx" export default { docs: { files: "pages/**/*.{md,mdx}", // Your markdown files publishMarkdown: true, // Generate .md files llms: { llmsTxt: true, // Generate llms.txt llmsTxtFull: true, // Generate llms-full.txt includeProtected: false, // Exclude protected routes }, }, }; ``` All options are disabled by default. :::tip When enabled, markdown files are generated during build and deleted after creating the `llms.txt` files unless `publishMarkdown: true` is set (see [`publishMarkdown` docs](/docs/configuration/docs#publishmarkdown)). ::: ### `llmsTxt` **Type:** `boolean` **Default:** `false` Generates an `llms.txt` file with links to all documentation pages: ```markdown title="llms.txt" # Documentation - [Quickstart](/docs/quickstart.md): Get started with Zudoku - [Writing](/docs/writing.md): A guide to writing documentation ``` ### `llmsTxtFull` **Type:** `boolean` **Default:** `false` Generates `llms-full.txt` containing the complete content of all pages. ### `includeProtected` **Type:** `boolean` **Default:** `false` By default, protected routes are excluded. Set to `true` to include them in the generated files. ## Output Generated files are available in the output directory after build: ```text dist/ ├── llms.txt # Generated if llmsTxt: true ├── llms-full.txt # Generated if llmsTxtFull: true └── docs/ ├── quickstart.md # Generated if publishMarkdown: true ├── writing.md └── ... ``` **Important:** Individual `.md` files are only kept in the final build if `publishMarkdown: true`. If only `llmsTxt` or `llmsTxtFull` is enabled, the `.md` files are generated temporarily during the build but deleted after the `llms.txt` files are created. Redirect pages, error pages (400, 404, 500), and protected routes (unless `includeProtected: true`) are automatically excluded from all generated files. --- ## Document: /docs/configuration/footer Learn how to configure the footer in Zudoku, including columns, social links, copyright notice, and logo. Customize the footer's position and content with slots. URL: /docs/configuration/footer # Footer Configuration The footer is a customizable component that appears at the bottom of every page in your Zudoku site. You can configure various aspects of the footer including its position, columns, social links, copyright notice, and logo. ## Basic Configuration The footer is configured in your `zudoku.config.tsx` file under the `site.footer` property: ```tsx const config: ZudokuConfig = { site: { footer: { // Footer configuration goes here position: "center", copyright: `© ${new Date().getFullYear()} YourCompany LLC. All rights reserved.`, // Other options... }, }, // Other configuration... }; ``` ## Position You can control the horizontal alignment of the footer content using the `position` property: ```tsx footer: { position: "center"; // default // or position: "start"; // or position: "end"; } ``` This affects how the content in the footer's main row is positioned horizontally. ## Columns The footer can include multiple columns of links, each with its own title: ```tsx footer: { columns: [ { title: "Product", position: "center", // position in grid, optional: start, center, end links: [ { label: "Features", href: "/features" }, { label: "Pricing", href: "/pricing" }, { label: "Documentation", href: "/docs" }, { label: "GitHub", href: "https://github.com/org/repo" }, // Auto-detected as external ], }, { title: "Company", links: [ { label: "About", href: "/about" }, { label: "Blog", href: "/blog" }, { label: "Contact", href: "/contact" }, ], }, ]; } ``` Each column can have its own positioning with the `position` property, which can be `"start"`, `"center"`, or `"end"`. This controls how the column is positioned within the footer grid. ## Social Media Links You can add social media links to your footer: ```tsx footer: { social: [ { icon: "github", href: "https://github.com/yourusername", }, { icon: "x", href: "https://x.com/yourhandle", label: "Follow us", // optional label text }, ]; } ``` The `icon` property currently can be one of the following values:
- `"reddit"` - `"discord"` - `"github"` - `"x"` (Twitter) - `"linkedin"` - `"facebook"` - `"instagram"` - `"youtube"` - `"tiktok"` - `"twitch"` - `"pinterest"` - `"snapchat"` - `"whatsapp"` - `"telegram"`
Or you can provide a custom React component. ## Copyright Notice Add a copyright notice with the `copyright` property: ```tsx footer: { copyright: `© ${new Date().getFullYear()} YourCompany LLC. All rights reserved.`; } ``` ## Logo You can add a logo to your footer: ```tsx footer: { logo: { src: { light: "/path/to/light-logo.png", dark: "/path/to/dark-logo.png" }, alt: "Company Logo", width: "120px" // optional width } } ``` ## Customizing with Slot Zudoku provides `footer-before` and `footer-after` slots that allow you to insert custom content before or after the main footer columns: ```tsx // In your zudoku.config.tsx slots: { "footer-before": () => (

Custom pre-footer content

This appears before the columns

), "footer-after": () => (

Additional footer content

) } ``` ## Complete Example Here's a complete example showing all footer options: ```tsx footer: { position: "center", columns: [ { title: "Product", position: "start", links: [ { label: "Features", href: "/features" }, { label: "Pricing", href: "/pricing" }, { label: "Documentation", href: "/docs" } ] }, { title: "Resources", position: "center", links: [ { label: "Blog", href: "/blog" }, { label: "Support", href: "/support" }, { label: "GitHub", href: "https://github.com/yourusername" } // Auto-detected as external ] } ], social: [ { icon: "github", href: "https://github.com/yourusername" }, { icon: "linkedin", href: "https://linkedin.com/company/yourcompany", label: "LinkedIn" } ], copyright: `© ${new Date().getFullYear()} YourCompany LLC. All rights reserved.`, logo: { src: { light: "/images/logo-light.svg", dark: "/images/logo-dark.svg" }, alt: "Company Logo", width: "100px" } } ``` --- ## Document: Documentation URL: /docs/configuration/docs # Documentation Zudoku uses a file-based routing system for documentation pages, similar to many modern frameworks. This page explains how routing works and how to customize it. ## File Based Routing By default, Zudoku automatically creates routes for all Markdown and MDX files based on their file path. Files are served at URLs that match their file structure, minus the file extension. ### Basic Examples ```text title="File tree" pages/ ├── introduction.md → /introduction ├── quickstart.mdx → /quickstart ├── guides/ │ ├── getting-started.md → /guides/getting-started │ └── advanced.md → /guides/advanced └── api/ └── reference.md → /api/reference ``` ### File Extensions Both `.md` and `.mdx` files are supported: - `.md` files support standard Markdown with frontmatter - `.mdx` files support JSX components within Markdown The file extension is automatically removed from the URL. ## Custom Paths You can override the default file-based routing by specifying custom paths in your navigation configuration. When a file has a custom path, it will only be accessible at that custom path, not at its original file-based path. ### Navigation Configuration ```tsx {5-6,13-14} title="zudoku.config.tsx" showLineNumbers export default { navigation: [ { type: "doc", file: "guides/getting-started.md", path: "start-here", // Custom path label: "Start Here", }, { type: "category", label: "Advanced", link: { file: "guides/advanced.md", path: "advanced-guide", // Custom path for category link }, items: [ // ... other items ], }, ], }; ``` In this example: - `guides/getting-started.md` is accessible at `/start-here` (not `/guides/getting-started`) - `guides/advanced.md` is accessible at `/advanced-guide` (not `/guides/advanced`) ## Configuration Options Configure docs routing and behavior through the `docs` section in your config: ```tsx title="zudoku.config.tsx" export default { docs: { files: ["/pages/**/*.{md,mdx}"], defaultOptions: { toc: true, disablePager: false, showLastModified: true, suggestEdit: { url: "https://github.com/your-org/your-repo/edit/main/docs", text: "Edit this page", }, }, }, }; ``` ### `files` **Type:** `string | string[]` **Default:** `"/pages/**/*.{md,mdx}"` Glob patterns that specify which files to include as documentation pages. You can provide a single pattern or an array of patterns. ```tsx title="zudoku.config.tsx" // Single pattern docs: { files: "/content/**/*.md"; } // Multiple patterns docs: { files: ["/pages/**/*.{md,mdx}", "/guides/**/*.md", "/tutorials/**/*.mdx"]; } ``` ### `defaultOptions` Default options applied to all documentation pages. These can be overridden on individual pages using frontmatter. #### `toc` **Type:** `boolean` **Default:** `true` Whether to show the table of contents (TOC) by default. ```tsx title="zudoku.config.tsx" docs: { defaultOptions: { toc: false; // Hide TOC by default } } ``` #### `disablePager` **Type:** `boolean` **Default:** `false` Whether to disable the previous/next page navigation by default. ```tsx title="zudoku.config.tsx" docs: { defaultOptions: { disablePager: true; // Disable pager by default } } ``` #### `showLastModified` **Type:** `boolean` **Default:** `true` Whether to show the last modified date by default. ```tsx title="zudoku.config.tsx" docs: { defaultOptions: { showLastModified: true; // Show last modified date } } ``` #### `suggestEdit` **Type:** `{ url: string; text?: string }` **Default:** `undefined` Configuration for the "Edit this page" link. ```tsx title="zudoku.config.tsx" docs: { defaultOptions: { suggestEdit: { url: "https://github.com/your-org/your-repo/edit/main/docs", text: "Edit this page on GitHub" // Optional custom text } } } ``` The `url` should be a template where the file path will be appended. For example, if your docs are in a `docs/pages/` directory, the URL might be `https://github.com/your-org/your-repo/edit/main/docs/pages`. #### `fullWidth` **Type:** `boolean` **Default:** `false` Whether pages should use the full available width (hiding the table of contents sidebar) by default. When enabled, the table of contents is accessible via an "On this page" toggle in the page header. Combine with `toc: false` to hide the table of contents entirely. ```tsx title="zudoku.config.tsx" docs: { defaultOptions: { fullWidth: true, // Use full-width layout for all pages by default } } ``` #### `copyPage` **Type:** `boolean` **Default:** `undefined` Whether to show a copy button in the page header that allows users to copy the page markdown. This feature requires `publishMarkdown` to be enabled (see below). ```tsx title="zudoku.config.tsx" docs: { defaultOptions: { copyPage: true; // Enable copy button for all pages } } ``` The copy button provides: - A primary "Copy page" action that copies the markdown to clipboard - A dropdown with additional options: - Copy link to page - Open markdown file (requires `publishMarkdown: true`) - AI assistant options (Claude, ChatGPT by default — see [AI Assistants](./ai-assistants.md) to customize) > **Note:** The copy button requires `publishMarkdown: true` to be set in your docs config. If > `copyPage` is enabled but `publishMarkdown` is not, a warning will be displayed. ### `publishMarkdown` **Type:** `boolean` **Default:** `true` When enabled, generates `.md` files for each documentation page during build. Pages can then be accessed at their URL path with the `.md` extension appended (e.g., `/docs/quickstart.md`). ```tsx title="zudoku.config.tsx" docs: { publishMarkdown: true, } ``` The generated markdown files: - Have frontmatter removed for cleaner content - Are accessible at `{page-url}.md` in both development and production - Are required for the `copyPage` button functionality - Are used by LLM features (see [llms.txt configuration](/docs/configuration/llms) for more details) ### `llms` **Type:** `object` **Default:** `undefined` Configuration for generating LLM-friendly documentation files. See the [llms.txt configuration](/docs/configuration/llms) page for complete documentation. ```tsx title="zudoku.config.tsx" docs: { llms: { llmsTxt: true, // Generate llms.txt summary file llmsTxtFull: true, // Generate llms-full.txt with complete content includeProtected: false } } ``` ## Overriding Defaults You can override [default options](#defaultoptions) on individual pages using frontmatter: ```markdown --- toc: false disablePager: true showLastModified: false --- # My Page This page has custom options that override the defaults. ``` ## Route Resolution Zudoku resolves routes in the following order: 1. **Custom paths from navigation** - If a file has a custom path defined in navigation, it's served at that path 2. **File-based paths** - All other files are served at their file-based paths ## Best Practices 1. **Use descriptive file names** - File names become part of the URL, so make them clear and SEO-friendly 2. **Organize with folders** - Use folder structure to group related content 3. **Custom paths for better UX** - Use custom paths for important pages that need memorable URLs (sometimes also called slugs) 4. **Consistent naming** - Use consistent naming conventions for files and folders --- ## Document: /docs/configuration/build-configuration URL: /docs/configuration/build-configuration # Build Configuration The `zudoku.build.ts` file allows you to configure build-time settings and processors for your Zudoku project. This file is executed during the build process and can be used to transform your API schemas before they are used in the documentation. :::tip{title="Security Note"} Unlike `zudoku.config.ts` which runs in both client and server environments, `zudoku.build.ts` runs exclusively in Node.js during build time. This means: - Sensitive operations (like API calls, file system access) can safely be performed - No build-time code or data is included in the final client bundle - Environment variables and secrets can be safely accessed - No browser-specific APIs are available ::: ## File Location Create a file named `zudoku.build.ts` in the root of your project: ```bash your-project/ ├── zudoku.config.ts ├── zudoku.build.ts # <-- Add this file └── ... ``` ## Basic Configuration Here's a basic example of a build configuration file: ```ts import type { ZudokuBuildConfig } from "zudoku"; const buildConfig: ZudokuBuildConfig = { processors: [ async ({ schema }) => { // Transform your schema here return schema; }, ], remarkPlugins: [], rehypePlugins: [], }; export default buildConfig; ``` ## Configuration Options ### `processors` An array of functions that transform your API schemas. Each processor receives: - `file`: The path to the schema file - `schema`: The OpenAPI schema object - `params`: Query parameters extracted from the input string (see [Splitting Schemas](../guides/processors#using-query-parameters-to-split-schemas)) - `dereference`: A function to dereference the schema Processors are executed in order, and each processor receives the output of the previous one. :::tip For detailed information about processors and available built-in processors, see the [Schema Processors](../guides/processors) guide. ::: Here's a simple example that adds a description to all operations: ```ts 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) { operation.description = "This is a public API endpoint"; } }); }); return schema; } export default { processors: [addDescriptionProcessor], }; ``` ### `remarkPlugins` An array of [Remark](https://github.com/remarkjs/remark) plugins to transform Markdown content. These plugins run before the content is converted to HTML. ```ts import remarkContributors from "remark-contributors"; export default { remarkPlugins: [remarkContributors], }; ``` ### `rehypePlugins` An array of [Rehype](https://github.com/rehypejs/rehype) plugins to transform HTML content. These plugins run after Markdown is converted to HTML. ```ts import rehypeKatex from "rehype-katex"; export default { rehypePlugins: [rehypeKatex], }; ``` ### `prerender` Configuration for the prerendering process that generates static HTML pages during build time. - `workers`: Number of parallel workers to use for prerendering. Defaults to 80% of available CPU cores. ```ts import os from "node:os"; export default { prerender: { workers: 4, // Fixed number of workers }, }; // Or use a percentage of available CPU cores export default { prerender: { workers: Math.floor(os.cpus().length * 0.75), // Use 75% of available cores }, }; ``` --- ## Document: Overview URL: /docs/configuration/authentication # Overview If you use a managed authentication service, such as Auth0, Clerk, or OpenID, you can implement this into your site and allow users to browse and interact with your documentation and API reference in a logged in state. ## Configuration To implement the authentication option for your site, add the `authentication` property to the [Zudoku Configuration](./overview.md) file. The configuration is slightly different depending on the authentication provider you use. ## Authentication Providers Zudoku supports Clerk, Auth0, Supabase, Firebase, Azure B2C, and any OpenID Connect provider (including Okta, Keycloak, Authentik, and PingFederate). Not seeing your authentication provider? [Let us know](https://github.com/zuplo/zudoku/issues) ### Auth0 For Auth0, you will need the `clientId` associated with the domain you are using. You can find this in the Auth0 dashboard under [Application Settings](https://auth0.com/docs/get-started/applications/application-settings). ```typescript { // ... authentication: { type: "auth0", domain: "yourdomain.us.auth0.com", clientId: "", scopes: ["openid", "profile", "email", "custom_scope"], }, // ... } ``` To setup Auth0, create a Single Page Application (SPA) application in the Auth0 dashboard. Set the following options: - Callback URL to `https://your-site.com/oauth/callback`. - For development environments only, we recommend configuring your app to allow the a wildcard callback like `https://*.zuplo.app/oauth/callback` to allow for testing each environment. - For local development, set the callback url to `http://localhost:3000/oauth/callback`. - Add your site hostname (your-site.com) to the list of allowed CORS origins. ### Clerk For Clerk you will need the publishable key for your application. You can find this in the Clerk dashboard on the [API Keys](https://dashboard.clerk.com/last-active?path=api-keys) page. ```typescript { // ... authentication: { type: "clerk", clerkPubKey: "", // Optional. See: https://clerk.com/docs/backend-requests/jwt-templates jwtTemplateName: "dev-portal", }, // ... } ``` ### OpenID For authentication services that support OpenID, you will need to supply an `clientId` and `issuer`. ```typescript { // ... authentication: { type: "openid", clientId: "", issuer: "", scopes: ["openid", "profile", "email", "custom_scope"] // Optional custom scopes }, // ... } ``` When configuring your OpenID provider, you will need to set the following: - Callback or Redirect URI to `https://your-site.com/oauth/callback`. - If your provider supports wildcard callback urls, we recommend configuring your development identity provider to allow a wildcard callback like `https://*.zuplo.site/oauth/callback` to allow for testing each environment. - For local development set the callback url to `http://localhost:3000/oauth/callback`. - Add your site hostname (your-site.com) to the list of allowed CORS origins. By default, the scopes "openid", "profile", and "email" are requested. You can customize these by providing your own array of scopes. For provider-specific guides (Okta, Keycloak, etc.), see the [OpenID Connect setup page](./authentication-openid.md). ### Firebase For Firebase authentication, you will need your Firebase project configuration. You can find this in the Firebase console under Project Settings. ```typescript title="zudoku.config.ts" { // ... authentication: { type: "firebase", apiKey: "", authDomain: ".firebaseapp.com", projectId: "", appId: "", providers: ["google", "github", "password"], // Optional }, // ... } ``` The `providers` option configures which sign-in methods are available. Supported providers include: `google`, `facebook`, `twitter`, `github`, `microsoft`, `apple`, `yahoo`, `password`, and `emailLink`. For detailed setup instructions, see the [Firebase setup guide](./authentication-firebase.md). ### Supabase To use Supabase as your authentication provider, supply your project's URL, API key, and the OAuth providers to use. ```typescript title="zudoku.config.ts" { // ... authentication: { type: "supabase", providers: ["github"], supabaseUrl: "https://your-project.supabase.co", supabaseKey: "", redirectToAfterSignUp: "/", redirectToAfterSignIn: "/", redirectToAfterSignOut: "/", }, // ... } ``` The `providers` option accepts an array of Supabase Auth's supported providers, such as `apple`, `azure`, `bitbucket`, `discord`, `facebook`, `figma`, `github`, `gitlab`, `google`, `kakao`, `keycloak`, `linkedin`, `linkedin_oidc`, `notion`, `slack`, `slack_oidc`, `spotify`, `twitch`, `twitter`, `workos`, `zoom`, or `fly`. ### Azure B2C For Azure B2C authentication, you will need to provide your Azure B2C tenant name, client ID, and policy name. ```typescript title="zudoku.config.ts" { // ... authentication: { type: "azureb2c", clientId: "", tenantName: "", policyName: "", issuer: "", scopes: ["openid", "profile", "email", "custom_scope"] }, // ... } ``` When configuring your Azure B2C application, you will need to set the following: - Redirect URI to `https://your-site.com/oauth/callback` - For local development, set the redirect URI to `http://localhost:3000/oauth/callback` - Add your site hostname (your-site.com) to the list of allowed CORS origins - Configure the appropriate user flows (policies) in your Azure B2C tenant By default, the scopes "openid", "profile", and "email" are requested. You can customize these by providing your own array of scopes. ## User Data After the user authenticates, the user profile is loaded via the provider's [User Info endpoint](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo). The following fields are used to display the user profile: - `name` - The user's full name - `email` - The user's email address - `picture` - The user's profile picture URL - `email_verified` - Whether the user's email address has been verified If the provider does not return a field, it will be left blank. ## Protected Routes Once authentication is configured, you can protect specific routes in your documentation to require users to be authenticated or meet custom authorization requirements. Routes can be protected with a simple array of patterns, or with custom callback functions that support reason codes for distinguishing between unauthorized and forbidden access. ```typescript title="zudoku.config.ts" { // ... // Simple array format: requires authentication protectedRoutes: [ "/admin/*", "/settings", "/api/*", ], // Or object format: custom authorization with reason codes protectedRoutes: { "/admin/*": ({ auth, reasonCode }) => !auth.isAuthenticated ? reasonCode.UNAUTHORIZED : auth.profile?.email?.endsWith("@example.com") ? true : reasonCode.FORBIDDEN, }, // ... } ``` See the [Protected Routes](./protected-routes.md) documentation for detailed information on configuring route protection, reason codes, and navigation behavior. --- ## Document: Supabase Setup Learn how to set up Supabase authentication for Zudoku using email and password or OAuth providers for secure documentation access. URL: /docs/configuration/authentication-supabase # Supabase Setup Supabase is an open-source Firebase alternative that provides authentication, database, and storage services. This guide shows you how to integrate Supabase authentication with your Zudoku documentation site. You can use **email and password** (Supabase signs users in with an email address and password), **OAuth providers** (GitHub, Google, and so on), or both. Follow the section that matches how you want users to sign in. ## Prerequisites You'll need a Supabase project. If you don't have one, [create a free Supabase project](https://supabase.com/dashboard) to get started. ## Setup Steps for Email and Password Use this path when users should sign up and sign in with an email address and password managed by Supabase. 1. **Enable email authentication in Supabase** In your [Supabase Dashboard](https://supabase.com/dashboard): - Select the project you are going to use - Go to **Authentication** → **Configuration** → **Sign In / Providers** - Under **Email**, ensure **Email sign-in** is enabled - Decide whether new users must confirm their email before signing in (see **Confirm email** in the same area). If confirmation is required, Supabase sends a link that returns users to your Zudoku portal (see [Authentication Routes](#authentication-routes)) 2. **Copy your Supabase URL and publishable key** From your Supabase project dashboard: - Go to **Project Settings** → **Integrations** → **Data API** - Copy your **API URL** (looks like `https://your-project-id.supabase.co`) - Go to **Configuration** → **API Keys** - Copy your **publishable** key (looks like `sb_publishable_...`) 3. **Configure Zudoku** Add the following to your [Zudoku configuration file](./overview.md). Omit `providers` or set it to an empty array when you are only using email and password: ```ts title="zudoku.config.ts" export default { // ... other configuration authentication: { type: "supabase", providers: [], supabaseUrl: "https://your-project.supabase.co", supabaseKey: "", }, // ... other configuration }; ``` 4. **Install Supabase dependencies** Install the packages Zudoku expects for the Supabase client and auth UI: ```bash npm install @supabase/supabase-js ``` 5. **Configure redirect URLs in Supabase** Supabase only redirects to URLs you allow. This matters for **email confirmation**, **password reset**, and **magic links** (if you use them elsewhere). From your Supabase project dashboard, go to **Authentication** → **Configuration** → **URL Configuration** and set: - **Site URL**: Your primary deployed URL (for example `https://docs.example.com`), or `http://localhost:3000` while developing locally. - **Redirect URLs**: Include every origin where your docs run, with a path wildcard so deep links work, for example: - Production: `https://docs.example.com/**` - Local dev: `http://localhost:3000/**` Use the same origins and [base path](./overview.md) you use in the browser. ## Setup Steps for Authentication Providers Use this path when users should sign in with OAuth (for example GitHub or Google). Set `onlyThirdPartyProviders: true` if you do not want email and password fields on the sign-in and sign-up pages. 1. **Configure Authentication Provider** In your [Supabase Dashboard](https://supabase.com/dashboard): - Select the Supabase project you are going to use - Navigate to **Authentication** → **Configuration** → **Sign In / Providers** - Enable your preferred authentication provider (GitHub, Google, Azure, etc.) - Follow the Supabase configuration documentation for that provider 2. **Copy your Supabase URL and publishable key** From your Supabase project dashboard: - Go to **Project Settings** → **Integrations** → **Data API** - Copy your **API URL** (looks like `https://your-project-id.supabase.co`) - Go to **Configuration** → **API Keys** - Copy your **publishable** key (looks like `sb_publishable_...`) 3. **Configure Zudoku** Add the following to your [Zudoku configuration file](./overview.md), using the API URL and publishable key from the previous step. Include every OAuth provider you enabled in Supabase in the `providers` array (see [Supported Providers](#supported-providers)): ```ts title="zudoku.config.ts" export default { // ... other configuration authentication: { type: "supabase", providers: ["github"], // one or more providers — required for OAuth sign-in supabaseUrl: "https://your-project.supabase.co", supabaseKey: "", }, // ... other configuration }; ``` 4. **Install Supabase dependencies** Install the Supabase client and auth UI packages your Zudoku project expects: ```bash npm install @supabase/supabase-js ``` 5. **Configure redirect URLs in Supabase** Supabase only redirects back to URLs you allow. Add every environment where users sign in: From your Supabase project dashboard, go to **Authentication** → **Configuration** → **URL Configuration** and update: - **Site URL**: Your primary deployed URL (for example `https://docs.example.com`), or `http://localhost:3000` while developing locally. - **Redirect URLs**: Include your Zudoku site origin(s), for example: - Production: `https://docs.example.com/**` - Local dev: `http://localhost:3000/**` - Preview deployments: a pattern your host uses, such as `https://*.vercel.app/**`, if applicable. Use the same paths you use in the browser (including any [base path](./overview.md) prefix). If OAuth redirects fail or send users to the wrong host, this section is usually the cause. ## Supported Providers Supabase supports numerous authentication providers. Use any of these values in the `providers` array: - `apple` - Sign in with Apple - `azure` - Microsoft Azure AD - `bitbucket` - Bitbucket - `discord` - Discord - `facebook` - Facebook - `figma` - Figma - `github` - GitHub - `gitlab` - GitLab - `google` - Google - `kakao` - Kakao - `keycloak` - Keycloak - `linkedin` / `linkedin_oidc` - LinkedIn - `notion` - Notion - `slack` / `slack_oidc` - Slack - `spotify` - Spotify - `twitch` - Twitch - `twitter` - Twitter/X - `workos` - WorkOS - `zoom` - Zoom - `fly` - Fly.io ## Email and Password with OAuth To offer **both** email/password and OAuth buttons, leave `onlyThirdPartyProviders` unset (or set it to `false`) and list your OAuth providers in `providers`. Complete [Enable email authentication in Supabase](#configure-email-sign-in) and [Configure Authentication Provider](#configure-authentication-provider) as needed, then use a config similar to: ```ts title="zudoku.config.ts" export default { authentication: { type: "supabase", providers: ["github", "google"], supabaseUrl: "https://your-project.supabase.co", supabaseKey: "", }, }; ``` ## Configuring Multiple Providers Complete [Configure Authentication Provider](#configure-authentication-provider) in the Supabase dashboard for each provider you need, then list them in the `providers` array: ```ts title="zudoku.config.ts" export default { // ... other configuration authentication: { type: "supabase", onlyThirdPartyProviders: true, providers: ["github", "google", "azure"], supabaseUrl: "https://your-project.supabase.co", supabaseKey: "", }, // ... other configuration }; ``` ## Configuration Options All available configuration options for Supabase authentication: ```ts title="zudoku.config.ts" authentication: { type: "supabase", // OAuth providers to show as buttons (optional for email/password-only; required for OAuth) // See Supported Providers — values must match Supabase provider IDs providers: ["google", "github"], // Supabase credentials (required) supabaseUrl: "https://your-project.supabase.co", supabaseKey: "", // Optional: Custom base path for auth routes (default: "/") basePath: "/docs", // Optional: When true, sign-in and sign-up pages show only OAuth buttons (no email/password) onlyThirdPartyProviders: true, // Optional: When true, the sign-up UI is disabled (for invite-only setups). // Must also be disabled in the Supabase dashboard under // Authentication → Configuration → Sign In / Providers → Allow new users to sign up. // Defaults to false. disableSignUp: true, // Optional: send Register to a separate URL instead of /signup // (absolute URL → external redirect, relative path → in-app navigate) signUp: { url: "/register" }, // Optional: Redirect URLs after authentication events redirectToAfterSignUp: "/welcome", redirectToAfterSignIn: "/dashboard", redirectToAfterSignOut: "/", } ``` ### Authentication Routes The Supabase authentication provider automatically creates the following routes: - `/signin` - Sign in (email and password when enabled, plus any configured OAuth providers) - `/signup` - Sign up (same as sign-in) - `/signout` - Sign out If you configure a custom `basePath`, these routes will be prefixed with that path (e.g., `/docs/signin`). ## Advanced Configuration ### User Profile Data Zudoku automatically extracts the following user information from Supabase authentication: - `sub` - User ID from Supabase - `email` - User's email address - `name` - User's full name (from `user_metadata.full_name` or `user_metadata.name`) - `emailVerified` - Whether the email has been confirmed - `pictureUrl` - User's avatar URL (from `user_metadata.avatar_url`) ### Additional User Metadata To store and access additional user information beyond what's provided by Supabase authentication: 1. Create a `profiles` table in your Supabase database 2. Set up a database trigger to create profile records on user signup 3. Use the Supabase client to query this data in your application Example profile table structure: ```sql CREATE TABLE profiles ( id UUID REFERENCES auth.users ON DELETE CASCADE, username TEXT UNIQUE, bio TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), PRIMARY KEY (id) ); -- Create trigger to automatically create profile on user signup CREATE OR REPLACE FUNCTION public.handle_new_user() RETURNS TRIGGER AS $$ BEGIN INSERT INTO public.profiles (id) VALUES (new.id); RETURN new; END; $$ LANGUAGE plpgsql SECURITY DEFINER; CREATE TRIGGER on_auth_user_created AFTER INSERT ON auth.users FOR EACH ROW EXECUTE PROCEDURE public.handle_new_user(); ``` ## Invite-only sign-ups To launch an invite-only portal where new users can only be created by an admin, set `disableSignUp: true` in your Zudoku config **and** disable new sign-ups in the Supabase dashboard (**Authentication** → **Configuration** → **Sign In / Providers** → clear **Allow new users to sign up**). When `disableSignUp` is `true`: - The "Don't have an account? Sign up." link is hidden on the sign-in page. - The Register button is hidden on the protected-route login dialog. - Navigating to `/signup` shows an "Invitation required" message instead of a form. ```ts title="zudoku.config.ts" export default { authentication: { type: "supabase", supabaseUrl: "https://your-project.supabase.co", supabaseKey: "", disableSignUp: true, }, }; ``` Create users directly from the Supabase dashboard (**Authentication** → **Users** → **Add user**) or via the Supabase admin API. ## Troubleshooting ### Common Issues 1. **OAuth sign-in shows no provider buttons**: For OAuth-only setups, add at least one provider ID to the `providers` array that matches a provider you enabled in Supabase. For **email and password only**, you can use `providers: []` — see [Setup Steps for Email and Password](#setup-steps-for-email-and-password). 2. **Email/password fields when you want OAuth only**: Set `onlyThirdPartyProviders: true` so the sign-in and sign-up screens show only OAuth buttons. Leave this unset (or `false`) when you want email and password fields — see [Setup Steps for Email and Password](#setup-steps-for-email-and-password). 3. **Invalid API key**: Use the **publishable** client key in `supabaseKey`, not the **secret** (service role) key. 4. **Provider Not Working**: Verify the provider is enabled in your Supabase dashboard and properly configured with the correct redirect URLs. 5. **Redirect URLs**: For local development, update your redirect URLs in both Supabase and the OAuth provider to include `http://localhost:3000`. 6. **CORS Errors**: Check that your site's domain is properly configured in Supabase's allowed URLs under **Authentication** → **URL Configuration**. 7. **Authentication Not Working**: Make sure you have installed `@supabase/supabase-js` in your project dependencies. ## Next Steps - Explore [Supabase Auth documentation](https://supabase.com/docs/guides/auth) for advanced features - Learn about [protecting routes](./authentication.md#protected-routes) in your documentation --- ## Document: PingFederate Setup Learn how to set up PingFederate authentication for Zudoku using OpenID Connect for enterprise-grade single sign-on. URL: /docs/configuration/authentication-pingfederate # PingFederate Setup PingFederate is an enterprise federation server that enables secure single sign-on (SSO) and API security for organizations. This guide walks you through integrating PingFederate with Zudoku using OpenID Connect. ## Prerequisites - Access to a PingFederate server (version 9.0 or later recommended) - Administrative access to configure OAuth clients in PingFederate - Your PingFederate server's base URL ## Setup Steps 1. **Create an OAuth Client in PingFederate** In the PingFederate administrative console: - Navigate to **Applications** → **OAuth** → **Clients** - Click **Add Client** - Configure the client: - **Client ID**: Choose a unique identifier (e.g., `zudoku-docs`) - **Client Authentication**: Select **None (Public Client)** - **Grant Types**: Enable **Authorization Code** and **Refresh Token** - **Redirect URIs**: - Production: `https://your-site.com/oauth/callback` - Preview (wildcard): `https://*.your-domain.com/oauth/callback` - Local Development: `http://localhost:3000/oauth/callback` 2. **Configure OpenID Connect Settings** Still in the OAuth client configuration: - Enable **OpenID Connect** - Configure scopes: - Enable `openid`, `profile`, and `email` (minimum required) - Add any custom scopes your organization requires - **ID Token Signing Algorithm**: RS256 (recommended) - **Access Token Manager**: Select or create an appropriate token manager - Save the configuration 3. **Configure Zudoku** Add the PingFederate configuration to your [Zudoku configuration file](./overview.md): ```typescript // zudoku.config.ts export default { // ... other configuration authentication: { type: "openid", clientId: "zudoku-docs", // Your OAuth client ID issuer: "https://pingfederate.your-domain.com", // Your PingFederate base URL scopes: ["openid", "profile", "email"], // Optional: add custom scopes }, // ... other configuration }; ``` ## Configuration Options ### Custom Scopes If your organization uses custom scopes for authorization, include them in the configuration: ```typescript authentication: { type: "openid", clientId: "zudoku-docs", issuer: "https://pingfederate.your-domain.com", scopes: ["openid", "profile", "email", "groups", "roles", "api:read"], } ``` ## Advanced Configuration ### CORS Configuration Configure CORS in PingFederate for your documentation site: 1. Navigate to **System** → **Server Configuration** → **Cross-Origin Resource Sharing** 2. Add your site's domain to the allowed origins: - `https://your-site.com` - `http://localhost:3000` (for local development) ### Attribute Mapping PingFederate can map user attributes from various sources. Ensure these standard claims are mapped: 1. Go to **OAuth** → **Access Token Management** → **Your Token Manager** 2. Configure attribute mappings: - `sub` → User's unique identifier - `name` → User's display name - `email` → User's email address - `picture` → User's profile picture URL (if available) ## Troubleshooting ### Common Issues 1. **Discovery Endpoint Not Found**: Ensure your issuer URL is correct and accessible. The OpenID Connect discovery endpoint should be available at `https://your-pingfederate-server/.well-known/openid-configuration`. 2. **Invalid Client Configuration**: Verify that the client ID matches exactly and that the redirect URIs are properly configured in PingFederate. 3. **CORS Errors**: Check that your site's domain is added to PingFederate's CORS configuration. 4. **Missing User Attributes**: Ensure attribute mappings are configured in your Access Token Manager. 5. **Token Validation Errors**: Verify that your PingFederate server's certificates are valid and that clock synchronization is accurate. ## Security Considerations - Always use HTTPS for production deployments - Regularly rotate signing certificates in PingFederate - Configure appropriate session timeouts - Review and audit OAuth client configurations periodically ## Next Steps - Review [PingFederate documentation](https://docs.pingidentity.com/pingfederate/) for advanced features - Learn about [protecting routes](./authentication.md#protected-routes) in your documentation - Configure group-based access control using PingFederate claims --- ## Document: OpenID Connect (OIDC) Configure any OpenID Connect compliant identity provider (Okta, Keycloak, Authentik, etc.) as the authentication provider for Zudoku. URL: /docs/configuration/authentication-openid # OpenID Connect (OIDC) Zudoku supports any identity provider that implements the [OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html) protocol via the generic `openid` provider type. This includes Okta, Keycloak, Authentik, Ory, ZITADEL, AWS Cognito, Google Identity, and most enterprise IdPs. ## Configuration Add the `authentication` property to your [Zudoku configuration](./overview.md): ```typescript title="zudoku.config.ts" { // ... authentication: { type: "openid", clientId: "", issuer: "", scopes: ["openid", "profile", "email"], // Optional }, // ... } ``` | Option | Required | Description | | ---------- | -------- | -------------------------------------------------------------------------------------------- | | `clientId` | Yes | The OAuth client ID issued by your provider. | | `issuer` | Yes | The issuer URL. Zudoku discovers endpoints from `/.well-known/openid-configuration`. | | `scopes` | No | Scopes to request. Defaults to `["openid", "profile", "email"]`. | ## Provider Setup Register Zudoku as a public SPA / single page application client in your identity provider and set: - Callback / Redirect URI to `https://your-site.com/oauth/callback` - For local development, add `http://localhost:3000/oauth/callback` - If your provider supports wildcards, add `https://*.your-domain.com/oauth/callback` for preview environments - Add your site origin to the list of allowed CORS origins - Enable the `Authorization Code` grant with PKCE and the `Refresh Token` grant ### Okta 1. In the Okta admin console go to **Applications** → **Applications** → **Create App Integration**. 2. Select **OIDC - OpenID Connect** and **Single Page Application**. 3. Set **Sign-in redirect URIs** to `https://your-site.com/oauth/callback` (add `http://localhost:3000/oauth/callback` for local development). 4. Under **Assignments**, assign the users or groups that should have access. 5. After creating the app, copy the **Client ID**. Your issuer is your Okta domain, for example `https://your-tenant.okta.com` or a custom authorization server like `https://your-tenant.okta.com/oauth2/default`. 6. Under **Security** → **API** → **Trusted Origins**, add your site origin for both CORS and Redirect. ```typescript title="zudoku.config.ts" { authentication: { type: "openid", clientId: "", issuer: "https://your-tenant.okta.com/oauth2/default", scopes: ["openid", "profile", "email"], }, } ``` ### Keycloak Use the realm issuer URL: ```typescript title="zudoku.config.ts" { authentication: { type: "openid", clientId: "zudoku", issuer: "https://keycloak.example.com/realms/", }, } ``` In the realm, create a client with **Client type** `OpenID Connect`, **Access type** `public`, and enable **Standard Flow** (Authorization Code). ## Verifying the Issuer You can confirm your issuer URL is correct by opening `/.well-known/openid-configuration` in a browser. It should return a JSON document listing `authorization_endpoint`, `token_endpoint`, `userinfo_endpoint`, and `jwks_uri`. ## Customizing Sign-up By default Register and Sign in both call the OIDC authorize endpoint, so users land on the same login page. Two options change that: ```typescript title="zudoku.config.ts" { authentication: { type: "openid", clientId: "", issuer: "", // Send Register to a separate URL (absolute → external redirect, relative → in-app navigate) signUp: { url: "/register" }, // Or pass extra params to the authorize URL on sign-up only (e.g. Keycloak) signUp: { authorizationParams: { kc_action: "register" } }, // Hide Register UI entirely. Visual only — still configure your IdP to block sign-ups. disableSignUp: true, }, } ``` When `disableSignUp` is `true`, the Register button on the protected-route login dialog is hidden and `/signup` shows an "Invitation required" page. ## User Profile After sign-in Zudoku calls the provider's [UserInfo endpoint](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo) and reads `name`, `email`, `picture`, and `email_verified` from the response. Map these claims in your provider if they are not emitted by default. ## Troubleshooting - **Discovery fails**: verify `/.well-known/openid-configuration` resolves and matches the `issuer` value in the document. - **CORS errors on token / userinfo**: add your site origin to the provider's allowed origins. - **Redirect URI mismatch**: the URI registered with the provider must match the Zudoku origin exactly, including protocol and port. - **Missing profile fields**: ensure `profile` and `email` scopes are granted and that the provider includes `name`, `email`, and `picture` claims in the UserInfo response. --- ## Document: Firebase Setup Learn how to set up Firebase Authentication for Zudoku, leveraging Google's secure authentication infrastructure with multiple sign-in providers. URL: /docs/configuration/authentication-firebase # Firebase Setup Firebase Authentication provides a comprehensive identity solution from Google, supporting email/password authentication and federated identity providers like Google, Facebook, Twitter, and more. This guide shows you how to integrate Firebase Authentication with your Zudoku documentation site. ## Prerequisites - A Google account to access Firebase Console - A Firebase project (free tier available) - Basic knowledge of Firebase configuration ## Setup Firebase 1. Go to the [Firebase Console](https://console.firebase.google.com/): 2. Click **Create a project** (or select an existing project) 3. Make sure authentication is enabled. Choose your preferred authentication providers. 4. Configure Authorized Domains, add your domains where the authentication will be used. 5. **Get Your Firebase Configuration** - Go to **Project settings** - Scroll to **Your apps** section - Click **Add app** → **Web** if you haven't already - Register your app with a nickname - Copy the Firebase configuration object ## Configure Zudoku Add the Firebase configuration to your [Zudoku configuration file](./overview.md): ```typescript title="zudoku.config.ts" export default { authentication: { type: "firebase", // Replace these with your Firebase project configuration // Get these values from Firebase Console > Project Settings apiKey: "", authDomain: "your-domain.firebaseapp.com", projectId: "your-project-id", appId: "1:296819355813:web:91d29f11cac6f073595d4c", // Optional fields storageBucket: "your-project.firebasestorage.app", messagingSenderId: "296819355813", measurementId: "G-12W6TTNR75", // Optional: specify which providers to show in the sign-in UI // Available providers: "google", "github", "facebook", "twitter", // "microsoft", "apple", "yahoo", "password", "emailLink" // If not specified, all enabled providers in Firebase will be available providers: ["google", "github", "password"], // Optional: disable the sign-up UI for invite-only setups. Defaults to false. // When true, the Register button and "Sign up" link are hidden, and /signup shows a message. // Visual only — also disable sign-ups in your Firebase project for real enforcement. disableSignUp: true, // Optional: send Register to a separate URL instead of /signup // (absolute URL → external redirect, relative path → in-app navigate) signUp: { url: "/register" }, // Optional: configure redirect URLs after authentication redirectToAfterSignIn: "/docs", redirectToAfterSignUp: "/getting-started", redirectToAfterSignOut: "/", }, }; ``` --- ## Document: Clerk Setup Learn how to set up Clerk authentication for Zudoku, providing a seamless authentication experience with modern UI components and extensive customization options. URL: /docs/configuration/authentication-clerk # Clerk Setup Clerk is a modern authentication platform that provides beautiful, customizable UI components and a developer-friendly experience. This guide walks you through integrating Clerk authentication with your Zudoku documentation site. ## Prerequisites If you don't have a Clerk account, you can sign up for a [free Clerk account](https://clerk.com/) that provides 10,000 monthly active users. ## Setup Steps 1. **Create a Clerk Application** In the [Clerk Dashboard](https://dashboard.clerk.com/): - Click **Create Application** - Enter your application name - Select your preferred authentication methods (email, social providers, etc.) - Click **Create Application** 2. **Create a Clerk JWT Template** You need to create a JWT Template so your JWTs include name, email and email_verified information. - Navigate to **JWT templates** in the [Clerk Dashboard](https://dashboard.clerk.com/) - Create a new template by clicking **Add new template** - Pick a name for the template - Add the following claims ```json { "name": "{{user.full_name}}", "email": "{{user.primary_email_address}}", "email_verified": "{{user.email_verified}}" } ``` - Save 3. **Configure Zudoku** Get your publishable key from the Clerk dashboard: - Navigate to **API Keys** in your Clerk dashboard - Copy the **Publishable key** Use the JWT template name defined in the previous section Add the Clerk configuration to your [Zudoku configuration file](./overview.md): ```typescript // zudoku.config.ts export default { // ... other configuration authentication: { type: "clerk", clerkPubKey: "", jwtTemplateName: "", }, // ... other configuration }; ``` 4. **Configure Redirect URLs (Optional)** If you need custom redirect behavior after sign-in or sign-up, you can configure this in your Zudoku config: ```typescript authentication: { type: "clerk", clerkPubKey: "", jwtTemplateName: "", redirectToAfterSignIn: "/docs", redirectToAfterSignUp: "/getting-started", redirectToAfterSignOut: "/", }, ``` You should also ensure your site's domain is added as an allowed origin in the Clerk dashboard. 5. **Customizing Sign-up (Optional)** To send Register to a different page, or hide it entirely: ```typescript authentication: { type: "clerk", clerkPubKey: "", // Absolute URL → external redirect, relative path → in-app navigate signUp: { url: "/register" }, // Hide Register UI. Visual only — configure Clerk to actually block sign-ups. disableSignUp: true, }, ``` ## Troubleshooting ### Common Issues 1. **Invalid Publishable Key**: Ensure you're using the publishable key (starts with `pk_`) and not the secret key. 2. **Authentication Not Working**: Verify that your Clerk application is active and not in development mode when deploying to production. 3. **Redirect Issues**: Check that your domain is added to the allowed redirect URLs in Clerk if using custom redirects. 4. **ReferenceError: can't access lexical declaration 'xxx' before initialization**: This can happen if the Clerk CDN script fails to load. Check your network connectivity and ensure your publishable key is valid. ## Next Steps - Explore [Clerk's documentation](https://clerk.com/docs) for advanced features - Learn about [protecting routes](./authentication.md#protected-routes) in your documentation - Configure [user roles and permissions](https://clerk.com/docs/organizations/roles-permissions) in Clerk --- ## Document: Azure AD Setup Learn how to set up Azure Active Directory (Microsoft Entra ID) authentication for Zudoku, enabling secure single sign-on for your organization. URL: /docs/configuration/authentication-azure-ad # Azure AD Setup Azure Active Directory (now Microsoft Entra ID) provides enterprise-grade authentication and authorization for organizations using Microsoft's cloud identity platform. This guide shows you how to integrate Azure AD with your Zudoku documentation site. ## Prerequisites - An Azure subscription with Azure Active Directory - Administrative access to register applications in Azure AD - Your Azure AD tenant ID ## Setup Steps 1. **Register an Application in Azure AD** In the [Azure Portal](https://portal.azure.com): - Navigate to **Azure Active Directory** → **App registrations** - Click **New registration** - Configure your application: - **Name**: Enter a descriptive name (e.g., "Zudoku Documentation") - **Supported account types**: Choose based on your needs: - Single tenant (your organization only) - Multitenant (any Azure AD directory) - Multitenant + personal Microsoft accounts - **Redirect URI**: - Platform: **Single-page application (SPA)** - URI: `https://your-site.com/oauth/callback` - Click **Register** 2. **Configure Authentication Settings** In your newly registered application: - Go to **Authentication** in the left menu - Under **Single-page application**, add redirect URIs: - Production: `https://your-site.com/oauth/callback` - Preview (wildcard): `https://*.your-domain.com/oauth/callback` - Local Development: `http://localhost:3000/oauth/callback` - **Implicit grant and hybrid flows** should remain **disabled** — Zudoku uses the more secure Authorization Code flow with PKCE - Configure **Supported account types** if needed - Save your changes 3. **Configure API Permissions (Optional)** If you need specific permissions: - Go to **API permissions** - Click **Add a permission** - Select **Microsoft Graph** → **Delegated permissions** - Add permissions like `User.Read`, `email`, `profile`, `openid` - Grant admin consent if required by your organization 4. **Configure Zudoku** Get your application details from the Azure Portal: - Go to **Overview** page of your app registration - Copy the **Application (client) ID** - Copy the **Directory (tenant) ID** Add the configuration to your [Zudoku configuration file](./overview.md): ```typescript // zudoku.config.ts export default { // ... other configuration authentication: { type: "openid", clientId: "", issuer: "https://login.microsoftonline.com//v2.0", scopes: ["openid", "profile", "email"], // Optional: customize scopes }, // ... other configuration }; ``` ## Configuration Options ### Single Tenant vs Multitenant For single tenant (organization-only access): ```typescript authentication: { type: "openid", clientId: "", issuer: "https://login.microsoftonline.com//v2.0", scopes: ["openid", "profile", "email"], } ``` For multitenant (any Azure AD organization): ```typescript authentication: { type: "openid", clientId: "", issuer: "https://login.microsoftonline.com/common/v2.0", scopes: ["openid", "profile", "email"], } ``` ### Custom Scopes and Permissions Request additional Microsoft Graph API scopes: ```typescript authentication: { type: "openid", clientId: "", issuer: "https://login.microsoftonline.com//v2.0", scopes: [ "openid", "profile", "email", "User.Read", "GroupMember.Read.All" // For group-based access control ], } ``` ### Protected Routes Protect specific documentation routes using the `protectedRoutes` configuration: ```typescript { // ... other configuration authentication: { type: "openid", // ... Azure AD config }, protectedRoutes: [ "/api/*", // Protect all API documentation "/internal/*", // Protect internal documentation "/admin/*" // Protect admin sections ], } ``` ## Advanced Configuration ### Conditional Access Policies Azure AD supports conditional access policies that can: - Require multi-factor authentication - Restrict access by location - Enforce device compliance - Control session lifetime Configure these in Azure AD Portal under **Security** → **Conditional Access**. ### App Roles and Groups To implement role-based access control: 1. In your app registration, go to **App roles** 2. Create custom roles (e.g., "Documentation.Read", "Documentation.Admin") 3. Assign roles to users or groups in **Enterprise applications** 4. Access role claims in your application ### B2B Guest Access To allow external partners access: 1. Enable B2B collaboration in Azure AD 2. Configure external collaboration settings 3. Invite guest users to your directory 4. Grant appropriate permissions to your application ### Customizing Sign-up Azure AD B2C usually handles sign-up via a separate user flow. To send users there, point Register at the URL of that flow (or any other page): ```typescript authentication: { type: "azureb2c", // ... // Absolute URL → external redirect, relative path → in-app navigate signUp: { url: "https://your-tenant.b2clogin.com/your-tenant.onmicrosoft.com/B2C_1_SignUp/oauth2/v2.0/authorize?..." }, // Hide Register entirely. Visual only — sign-ups are still controlled by your B2C policy. disableSignUp: true, } ``` ## User Data Azure AD provides rich user profile data through OpenID Connect: - `name` - User's display name - `email` - User's email address - `picture` - Profile picture URL (when available) - `email_verified` - Email verification status - `preferred_username` - User's UPN (User Principal Name) - Additional claims based on your API permissions ## Troubleshooting ### Common Issues 1. **Invalid Client Error**: Ensure the client ID is correct and the application is properly registered. 2. **Redirect URI Mismatch**: The redirect URI must exactly match one configured in Azure AD, including protocol and path. 3. **Tenant Access Issues**: For single-tenant apps, ensure users are from the correct tenant. For multi-tenant, verify the issuer URL uses "common" or "organizations". 4. **Missing User Information**: Check that required API permissions are granted and admin consent is provided if needed. 5. **Token Validation Errors**: Ensure your issuer URL is correct and includes the `/v2.0` endpoint for the Microsoft identity platform. 6. **Authentication Not Working**: Verify your issuer URL and client ID are correct, and that your app registration is configured as a Single-page application (SPA) with the correct redirect URIs. ## Security Best Practices - Use single-tenant configuration unless multi-tenant is specifically required - Implement conditional access policies for sensitive documentation - Regularly review and audit app permissions - Monitor sign-in logs in Azure AD for suspicious activity - Use app roles for fine-grained access control ## Next Steps - Explore [Microsoft identity platform documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/) - Learn about [protecting routes](./authentication.md#protected-routes) in your documentation - Implement [app roles](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-add-app-roles-in-azure-ad-apps) for advanced authorization --- ## Document: Auth0 Setup Learn how to set up Auth0 authentication for Zudoku, including application configuration and integration steps for secure API documentation access. URL: /docs/configuration/authentication-auth0 # Auth0 Setup Auth0 is a flexible authentication and authorization platform that integrates seamlessly with Zudoku. This guide walks you through setting up Auth0 authentication for your documentation site. ## Prerequisites If you don't have an Auth0 account, you can sign up for a [free Auth0 account](https://auth0.com/signup) that provides 7,000 monthly active users. ## Setup Steps 1. **Create Auth0 Application** [Create a new Auth0 application](https://auth0.com/docs/get-started/auth0-overview/create-applications) in the Auth0 dashboard: - Select type **Single Page Web Applications** - Give your application a descriptive name 2. **Configure Auth0 Application** In your Auth0 application settings, configure the following: **Application URLs:** - **Allowed Callback URLs**: - Production: `https://your-site.com/oauth/callback` - Preview (wildcard): `https://*.your-domain.com/oauth/callback` - Local Development: `http://localhost:3000/oauth/callback` - **Allowed Logout URLs**: - Production: `https://your-site.com/oauth/logout-callback` - Preview (wildcard): `https://*.your-domain.com/oauth/logout-callback` - Local Development: `http://localhost:3000/oauth/logout-callback` - **Allowed Web Origins**: - Production: `https://your-site.com` - Preview (wildcard): `https://*.your-domain.com` - Local development: `http://localhost:3000` **Refresh Token Rotation:** - **Allow Refresh Token Rotation**: Enabled - **Rotation Overlap Period**: 0 seconds (recommended) Keep the default **Refresh Token Expiration** settings unless you have specific requirements. 3. Create an Auth0 API: - Navigate to the [APIs section](https://manage.auth0.com/#/apis) in the Auth0 dashboard - Click **Create API** - Set a name (e.g., "Zudoku API") and an identifier (e.g., `https://your-domain.com/api`) - Choose **RS256** as the signing algorithm - Save the API :::warning This step is important. If you skip creating an API, Zudoku will not be able to validate the tokens issued by Auth0, leading to authentication failures. ::: 4. **Configure Zudoku** Add the Auth0 configuration to your [Zudoku configuration file](./overview.md): ```typescript // zudoku.config.ts export default { // ... other configuration authentication: { type: "auth0", domain: "your-domain.us.auth0.com", clientId: "", audience: "https://your-domain.com/api", // Your Auth0 API identifier }, // ... other configuration }; ``` Where: - **domain**: Your Auth0 domain (found in your application's Basic Information) - **clientId**: The Client ID from your Auth0 application settings - **audience**: The identifier of the Auth0 API you created (e.g., `https://your-domain.com/api`) ## Advanced Configuration ### Custom Scopes If you need additional scopes for your API access, you can specify them in the configuration: ```typescript authentication: { type: "auth0", domain: "your-domain.us.auth0.com", clientId: "", scopes: ["openid", "profile", "email", "read:api", "write:api"], } ``` ### Enabling Logout Zudoku supports logout functionality for Auth0 tenants. For tenants created **on or after November 14, 2023**, logout is automatically enabled through the OIDC [RP-Initiated Logout](https://auth0.com/docs/authenticate/login/logout/log-users-out-of-auth0) endpoint. To enable logout for your Auth0 application: 1. Ensure your **Allowed Logout URLs** are configured in Auth0 (see [Configure Auth0 Application](#setup-steps) above) 2. The logout URL must use the `/oauth/logout-callback` path (e.g., `https://your-site.com/oauth/logout-callback` for production) For older tenants, you may need to enable **RP-Initiated Logout** in your tenant settings. See the [Auth0 logout documentation](https://auth0.com/docs/authenticate/login/logout/log-users-out-of-auth0) for details. ### Customizing the Prompt Parameter By default, Zudoku sets `prompt="login"` in the Auth0 authorization request, which forces users to re-enter their credentials even if they have a valid session. You can customize this behavior using the `options.prompt` configuration: ```typescript authentication: { type: "auth0", domain: "your-domain.us.auth0.com", clientId: "", audience: "https://your-domain.com/api", options: { prompt: "", // Omit the prompt parameter to allow silent authentication }, } ``` Valid values for the `prompt` parameter include: - `"login"` - Force users to re-enter their credentials even if they have a valid session (default) - `"consent"` - Force users to consent to authorization even if they previously consented - `"select_account"` - Force users to select an account (useful for multi-account scenarios) - `"none"` - No prompt is shown; silent authentication only - `""` (empty string) - Omit the prompt parameter, allowing Auth0 to handle authentication based on session state When the prompt parameter is omitted (empty string), Auth0 will: - Silently authenticate the user if they have a valid session - Redirect to the login page if no valid session exists ### Customizing Sign-up By default Auth0 already adds `screen_hint=signup` when a user clicks Register. To send users to a different page (or hide Register entirely): ```typescript authentication: { type: "auth0", domain: "your-domain.us.auth0.com", clientId: "", // Send Register to a separate URL (absolute → external, relative → in-app) signUp: { url: "/register" }, // Or pass extra params to the Auth0 authorize URL on sign-up signUp: { authorizationParams: { connection: "your-signup-connection" } }, // Hide Register UI entirely. Visual only — configure Auth0 to actually block sign-ups. disableSignUp: true, } ``` ## Troubleshooting ### Common Issues 1. **Callback URL Mismatch**: Ensure your callback URLs in Auth0 exactly match your site's URL, including the `/oauth/callback` path. 2. **CORS Errors**: Add your site's domain to the Allowed Web Origins in Auth0. 3. **Authentication Loop**: Check that your Auth0 domain is a plain hostname only (e.g., `your-domain.us.auth0.com`) without a protocol prefix (`https://`) or trailing slash. 4. **Token Validation Errors**: Ensure the audience in your Zudoku configuration matches the identifier of the Auth0 API you created. ## Next Steps - Learn about [protecting routes](./authentication.md#protected-routes) in your documentation - Explore other [authentication providers](./authentication.md#authentication-providers) supported by Zudoku - Configure [user permissions](./authentication.md#user-data) based on Auth0 roles --- ## Document: API Reference Learn how to configure the `apis` setting in Zudoku to generate API reference documentation from OpenAPI files, including file and URL references, versioning, customization options, and OpenAPI extensions. URL: /docs/configuration/api-reference # API Reference The `apis` configuration setting in the [Zudoku Configuration](./overview.md) file allows you to specify the OpenAPI document that you want to use to generate your API reference documentation. There are multiple ways to reference an API file in the configuration including using a URL or a local file path. The OpenAPI document can be in either JSON or YAML format. ## File Reference You can reference a local OpenAPI document by setting the `type` to `file` and providing the path to the file. ```ts title=zudoku.config.ts const config = { // ... apis: { type: "file", input: "./openapi.json", // Supports JSON and YAML files (ex. openapi.yaml) path: "/api", }, // ... }; ``` ## URL Reference :::danger{title="Recommendation"} We strongly recommend using `type: "file"` for your OpenAPI schemas. When using URL based references, all schema processing occurs at runtime in the browser. This can cause noticeable performance issues with large OpenAPI documents and some features may not be fully supported due to the added complexity of runtime processing. ::: If your OpenAPI document is accessible elsewhere via URL you can use this configuration, changing the `input` value to the URL of your own OpenAPI document (you can use the Rick & Morty API document if you want to test and play around): ```ts title=zudoku.config.ts const config = { // ... apis: { type: "url", input: "https://rickandmorty.zuplo.io/openapi.json", path: "/api", }, // ... }; ``` :::caution{title="CORS Policy"} If you are using a URL to reference your OpenAPI document, you may need to ensure that the server hosting the document has the correct CORS policy in place to allow the Zudoku site to access it. ::: ## Versioning ### File-based Versioning When using `type: "file"`, you can provide an array of file paths to create versioned API documentation. Version metadata is automatically extracted from each OpenAPI schema at build time: ```ts title=zudoku.config.ts const config = { apis: { type: "file", input: [ // Order of the array determines the order of the versions "./openapi-v2.json", "./openapi-v1.json", ], path: "/api", }, }; ``` If you need to override version metadata, you can specify it explicitly: ```ts title=zudoku.config.ts const config = { apis: { type: "file", input: [ // Order of the array determines the order of the versions { path: "v2", label: "Version 2.0", input: "./openapi-v2.json", }, { path: "v1", label: "Version 1.0", input: "./openapi-v1.json", }, ], path: "/api", }, }; ``` You can specify: - `input`: Path to the OpenAPI document (required) - `path`: Version identifier used in the URL path (e.g., `/api/v2`) - `label`: Optional display name for the version selector You can also mix strings and objects in the array - use strings for defaults and objects when you need to customize: ```ts title=zudoku.config.ts const config = { apis: { type: "file", input: [ { path: "latest", label: "Latest (2.0)", input: "./openapi-v2.json", }, "./openapi-v1.json", // Uses info.version from the document ], path: "/api", }, }; ``` ### Splitting a Single Schema If you have one schema containing multiple API versions (e.g. `/v1/...` and `/v2/...` paths), you can split it into separate versions by appending query parameters to the input path: ```ts title=zudoku.config.ts const config = { apis: { type: "file", input: ["openapi.json?prefix=/v2", "openapi.json?prefix=/v1"], path: "/api", }, }; ``` The query parameters are passed to [schema processors](../guides/processors) via the `params` argument, where you can filter the schema based on their values. See [Using Query Parameters to Split Schemas](../guides/processors#using-query-parameters-to-split-schemas) for a full example. ### URL-based Versioning When using `type: "url"`, you can provide an array of version configurations. Since URL-based schemas cannot be processed at build time, you must explicitly specify the version identifier and optional label: ```ts title=zudoku.config.ts const config = { apis: { type: "url", input: [ { path: "v2", label: "Version 2.0", input: "https://api.example.com/openapi-v2.json", }, { path: "v1", label: "Version 1.0", input: "https://api.example.com/openapi-v1.json", }, ], path: "/api", }, }; ``` Each URL version object requires: - `path`: Version identifier used in the URL path (e.g., `/api/v2`) - `input`: URL to the OpenAPI document - `label`: Optional display name for the version selector (defaults to `path` if not provided) ## Options The `options` field allows you to customize the API reference behavior: ```ts title=zudoku.config.ts const config = { apis: { type: "file", input: "./openapi.json", path: "/api", options: { examplesLanguage: "shell", // Default language for code examples supportedLanguages: [ { value: "shell", label: "cURL" }, { value: "javascript", label: "JavaScript" }, ], disablePlayground: false, // Disable the interactive API playground disableSidecar: false, // Disable the sidecar completely disableSecurity: true, // Disable security scheme display and playground auth (default) showVersionSelect: "if-available", // Control version selector visibility expandAllTags: true, // Control initial expanded state of tag categories showInfoPage: true, // Always show the info page (unset = show only if a description is set) schemaDownload: { enabled: true, // Enable schema download button fileName: "schema", // Set name of the schema file when downloaded }, }, }, }; ``` Available options: - `examplesLanguage`: Set default language for code examples - `supportedLanguages`: Array of language options for code examples. Each option has `value` (code identifier) and `label` (display name) - `disablePlayground`: Disable the interactive API playground globally - `disableSidecar`: Disable the sidecar panel completely - `disableSecurity`: Disable OpenAPI security scheme display (auth badges on operations, security schemes section on the info page, and the Authorize dialog in the playground). Disabled by default (`true`). Set to `false` to enable security scheme support - `showVersionSelect`: Control version selector visibility - `"if-available"`: Show version selector only when multiple versions exist (default) - `"always"`: Always show version selector (disabled if only one version) - `"hide"`: Never show version selector - `expandAllTags`: Control initial expanded state of tag categories (default: `true`) - `showInfoPage`: Control the API information page shown as the index route. Set to `true` to always show it, or `false` to always redirect the API root to the first tag. When unset, the page is shown only if the API has a description, otherwise the API root redirects to the first tag - `schemaDownload`: Enable schema download functionality. When enabled, displays a button allowing users to download the OpenAPI schema, copy it to clipboard, or open in a new tab. - `enabled`: Enable or disable the schema download button - `fileName`: Set name of the schema file when downloaded (default: `schema`). Note: Do not include a file extension, as that is added automatically based on the input file type. - `transformExamples`: Function to transform request/response examples before rendering. See [Transforming Examples](../guides/transforming-examples.md) for detailed usage - `generateCodeSnippet`: Function to generate custom code snippets for the API playground. See [Advanced Configuration](#advanced-configuration) below ## Default Options Instead of setting options for each API individually, you can use `defaults.apis` to set global defaults that apply to all APIs: ```ts title=zudoku.config.ts const config = { defaults: { apis: { examplesLanguage: "shell", // Default language for code examples disablePlayground: false, // Disable the interactive API playground disableSidecar: false, // Disable the sidecar completely disableSecurity: true, // Disable security scheme display and playground auth (default) showVersionSelect: "if-available", // Control version selector visibility expandAllTags: false, // Control initial expanded state of tag categories showInfoPage: true, // Always show the info page (unset = show only if a description is set) schemaDownload: { enabled: true, // Enable schema download button fileName: "schema", // Set name of the schema file when downloaded }, }, }, apis: { type: "file", input: "./openapi.json", path: "/api", }, }; ``` Individual API options will override these defaults when specified. ## AI Assistants The schema download dropdown includes AI assistant options (Claude, ChatGPT) by default. You can customize or disable these using the top-level `aiAssistants` configuration. See [AI Assistants](./ai-assistants.md) for full documentation. ## Advanced Configuration ### Custom Code Snippets Use `generateCodeSnippet` to generate custom code snippets instead of the default HTTP examples. This is useful when you want to show SDK usage or language-specific implementations. ```tsx title=zudoku.config.tsx const config: ZudokuConfig = { apis: { type: "file", input: "./openapi.json", path: "/api", options: { supportedLanguages: [ { value: "js", label: "JavaScript" }, { value: "python", label: "Python" }, ], generateCodeSnippet: ({ selectedLang, selectedServer, operation, example }) => { if (operation.operationId === "createUser") { if (selectedLang === "js") { return ` import { Client } from "@mycompany/sdk"; const client = new Client({ baseUrl: "${selectedServer}" }); const user = await client.createUser(${JSON.stringify(example, null, 2)}); `.trim(); } if (selectedLang === "python") { return ` from mycompany import Client client = Client(base_url="${selectedServer}") user = client.create_user(${JSON.stringify(example)}) `.trim(); } } // Return false to use default snippet generation return false; }, }, }, }; ``` The function receives: - `selectedLang`: Currently selected language from `supportedLanguages` - `selectedServer`: Currently selected server URL - `operation`: The OpenAPI operation object - `example`: The current request body example Return a string with the custom snippet, or `false` to fall back to default generation. ## Extensions Zudoku supports OpenAPI extensions (properties starting with `x-`) to customize behavior at different levels of your API documentation. ### Operations - `x-zudoku-playground-enabled`: Control playground visibility for an operation (default: `true`) - `x-explorer-enabled`: Alias for `x-zudoku-playground-enabled` for compatibility Example: ```json { "paths": { "/users": { "get": { "summary": "Get users", "x-zudoku-playground-enabled": false // Disable playground for this operation } } } } ``` ### Tags Extensions that can be applied to tag categories: - `x-zudoku-collapsed`: Control initial collapsed state of a tag category (default: `true`) - `x-zudoku-collapsible`: Control if a tag category can be collapsed (default: `true`) Example: ```json { "tags": [ { "name": "Users", "x-zudoku-collapsed": false } ] } ``` ### Tag Groups Use `x-tagGroups` at the root of your OpenAPI document to group tags together in the navigation: ```yaml x-tagGroups: - name: Shipment tags: - Packages - Parcels - Letters ``` ## Metadata Your API reference page metadata is sourced directly from your OpenAPI spec. The [`info`](https://spec.openapis.org/oas/v3.1.0#info-object) object is used set the corresponding tags in the page's `head`. | Metadata Property | OpenAPI Property | Comment | | ----------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | title | `info.title` | If `metadata.title` is set as a template string (ex. `%s - My Company`) it will be used | | description | `info.summary` | `info.summary` is preferred as it is shorter and plaintext-only, but Zudoku will fall back to the `info.description` if no summary is provided | --- ## Document: API Catalog URL: /docs/configuration/api-catalog # API Catalog If you're dealing with multiple APIs and multiple OpenAPI files, the API Catalog comes in handy. It creates an overview of all your APIs and lets you organize them into categories and tags. ## Enable API Catalog To enable the API Catalog, add a `catalogs` object to your Zudoku configuration file. ```ts title=zudoku.config.ts const config = { // ... catalogs: { path: "/catalog", label: "API Catalog", }, // ... }; ``` You can then add your APIs to the catalog by adding the `categories` property to your API configuration. :::caution{title="Recommendation: nest API paths under the catalog path"} For a consistent user experience, APIs that appear in the catalog should have their `path` prefixed with the catalog path. For example, if your catalog is at `/catalog`, an API path should start with `/catalog/` (e.g., `/catalog/api-users`). APIs with paths outside the catalog path still appear in the catalog, but clicking them navigates the user outside the catalog section. ::: ```ts title=zudoku.config.ts const config = { catalogs: { path: "/catalog", label: "API Catalog", }, apis: [ { type: "file", input: "./operational.json", path: "/catalog/api-operational", // Must be under /catalog/ categories: [{ label: "General", tags: ["Operational"] }], }, { type: "file", input: "./enduser.json", path: "/catalog/api-enduser", // Must be under /catalog/ categories: [{ label: "General", tags: ["End-User"] }], }, { type: "file", input: "./openapi.json", path: "/catalog/api-auth", // Must be under /catalog/ categories: [{ label: "Other", tags: ["Authentication"] }], }, ], }; ``` To add the catalog to your navigation, use a link item: ```ts title=zudoku.config.ts const config = { navigation: [ { type: "link", label: "API Catalog", to: "/catalog", icon: "square-library", }, ], // ... catalogs and apis config }; ``` ## Advanced Configuration ### Filtering catalog items You can filter which APIs are shown in the catalog by using the `filterItems` property. The function receives the items and the catalog context (including `auth`) as arguments. Each item has a `categories` array where each category has a `label` and `tags`. ```ts title=zudoku.config.ts const config = { catalogs: { path: "/catalog", label: "API Catalog", filterItems: (items, { auth }) => { return items.filter((item) => item.categories?.some((category) => category.tags?.includes("public")), ); }, }, }; ``` ## Standalone APIs (without catalog) APIs that are **not** part of a catalog can use any path and will appear as standalone API reference pages. These APIs don't need `categories` and their paths don't need to be nested under a catalog. ```ts title=zudoku.config.ts const config = { apis: [ { type: "file", input: "./openapi.json", path: "/api", // standalone, not under a catalog }, ], navigation: [ { type: "link", label: "API Reference", to: "/api", }, ], }; ``` See the [API Reference](/docs/configuration/api-reference) page for full details on configuring individual APIs, including versioning and customization options. --- ## Document: AI Assistants Configure which AI assistant integrations appear in dropdown menus across your Zudoku documentation site, including built-in presets and custom providers. URL: /docs/configuration/ai-assistants # AI Assistants By default, Zudoku shows "Use in Claude" and "Use in ChatGPT" options in dropdown menus on both [API reference](./api-reference.md) and [documentation](./docs.md) pages. You can customize this behavior using the top-level `aiAssistants` configuration. ## Disable AI Assistants To remove all AI assistant options: ```ts title=zudoku.config.ts const config = { aiAssistants: false, // ... }; ``` ## Use Only Specific Presets ```ts title=zudoku.config.ts const config = { aiAssistants: ["claude"], // Only show Claude // ... }; ``` Available presets: `"claude"`, `"chatgpt"` ## Add Custom AI Assistants You can add custom entries with a label and URL. Use `{pageUrl}` as a placeholder in the URL string, or provide a callback for full control: ```ts title=zudoku.config.ts const config = { aiAssistants: [ "claude", // built-in preset { label: "Open in MyAI", // Simple string with placeholder url: "https://myai.com/?context={pageUrl}", }, { label: "Open in CustomAI", // Callback for full control url: ({ pageUrl, type }) => { if (type === "openapi") { return `https://custom.ai/?q=${encodeURIComponent("Explain this API: " + pageUrl)}`; } return `https://custom.ai/?q=${encodeURIComponent("Explain this page: " + pageUrl)}`; }, }, ], // ... }; ``` The callback receives `{ pageUrl: string, type: "docs" | "openapi" }` so you can customize behavior per context. --- ## Document: Authentication Providers & API Identities URL: /docs/concepts/auth-provider-api-identities # Authentication Providers & API Identities When building an API documentation portal, you often need to provide a way for users to authenticate their API requests. This typically involves managing API keys and different authentication identities. However, implementing a secure and user-friendly system for API key management can be complex and time-consuming. Zudoku provides a powerful solution to this problem through its API Keys and Identities system. ## Authentication Providers and API Identities :::tip **Authentication providers** allow your users to sign in to your documentation portal. **API Identities** allow your users to authenticate their API requests. ::: Before diving into API Identities, it's important to understand that Zudoku separates user authentication from API authentication. Authentication providers handle how users sign in to your documentation portal, while API Identities manage how these users interact with your APIs. Authentication providers (like Auth0 or custom JWT) handle: - User authentication for the documentation portal - Session management - User authorization and access control This separation allows you to: - Use different authentication methods for your portal and APIs - Manage API access independently of user authentication - Support multiple API authentication schemes simultaneously [Learn more about authentication providers](../configuration/authentication) ## Understanding API Identities API Identities in Zudoku represent different authentication contexts that can be used to make API requests. These could be different environments (production, staging), different authentication methods (API key, JWT), or different service accounts. ### The API Identity Interface ```typescript interface ApiIdentity { id: string; label: string; authorizeRequest: (request: Request) => Promise | Request; } interface ApiIdentityPlugin { getIdentities: () => Promise | ApiIdentity[]; } ``` Each API Identity consists of: - `id`: A unique identifier for the identity - `label`: A human-readable name shown in the UI - `authorizeRequest`: A function that modifies requests to include the necessary authentication ## Implementing API Identities In this example, we'll use Auth0 as our authentication provider and implement an API Identity for a demo API. :::note This example shows how to implement API Identities in Zudoku. If you're using Zuplo and you are using the built-in API Key management system, you don't need to implement this yourself. Zuplo will handle API Identities for you. ::: To add API Identities to your Zudoku configuration, you need to implement the `ApiIdentityPlugin` interface. Here's an example: ```typescript import { createApiIdentityPlugin } from "zudoku/plugins"; export default { authentication: { type: "auth0", domain: "my-domain.auth0.com", clientId: "my-client-id", }, plugins: [ createApiIdentityPlugin({ getIdentities: async (context) => [ { id: "api-key-one", label: "My API Key", authorizeRequest: (request) => { // We get the access token from the // authentication provider (Auth0) and add it to the request headers return context.authentication?.signRequest(request); }, }, ], }), ], }; ``` When implemented, this identity will appear in the Zudoku playground. ## Using API Identities in custom plugins Plugins that ship their own playground (like the GraphQL plugin) can reuse the identity system instead of building their own authentication handling: - `useApiIdentitySelection` from `zudoku/hooks` exposes the user's identity selection. The selection is shared across all playgrounds and persists for the session. - `` from `zudoku/components` renders the button and popover used to pick an identity. It renders nothing when no identities are available. ```tsx import { ApiIdentityPicker } from "zudoku/components"; import { useApiIdentitySelection } from "zudoku/hooks"; const Playground = ({ endpoint }: { endpoint: string }) => { const { authorizeRequest } = useApiIdentitySelection(); const execute = async (body: string) => { const request = new Request(endpoint, { method: "POST", body }); // Applies the selected identity; returns the request unchanged if none is selected const response = await fetch(await authorizeRequest(request)); return response.json(); }; return (
{/* ... */}
); }; ``` --- ## 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