ZuploZuplo
LoginSign Up
  • Documentation
  • API Reference
Introduction
Getting Started
    Develop using the Portal
      1 - Setup Your Gateway2 - Rate Limiting3 - API Key Auth4 - Deploy5 - Dynamic Rate LimitingMCP - Quick start
    Develop Locally
      1 - Setup Your Gateway2 - Rate Limiting3 - API Key Auth
Concepts
Development
Policies
Handlers
API Keys
MCP Server
MCP Gateway
AI Gateway
Developer Portal
    IntroductionLocal DevelopmentUpdating VersionsNode Modules & Customization
    Configuration
      Colors & ThemeDocumentationNavigationBranding & LayoutSearchFooterllms.txt
    Writing
    OpenAPI
    Authentication
    Integrations
    Guides
    Extending
    Components
Monetization
Deploying & Source Control
Observability
Networking & Infrastructure
Account Management
Programming API
Build with AI
Zuplo CLI
Migration Guides
Platform LimitsSecuritySupportTrust & ComplianceChangelog
powered by Zudoku
Configuration

Navigation

Dev Portal 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 Dev Portal config file. Each item can be one of several types. At the simplest level you may only have links and categories.

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
  • 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.

Code
{ "type": "link", "label": "Support", "to": "/my/api" // or: https://example.com/my-external-link }
TypeScript type declaration
Code
type NavigationLink = { type: "link"; to: string; label: string; icon?: string; // Lucide icon name target?: "_self" | "_blank"; 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).

Code
{ "type": "category", "label": "Getting Started", "collapsible": true, // optional "collapsed": false, // optional "items": [ { "type": "link", "label": "Support", "to": "https://support.example.com" } ] }
TypeScript type declaration
Code
type NavigationCategory = { type: "category"; icon?: string; // Lucide icon name items: Array<NavigationItem>; // any navigation item type, including string shorthands for docs label: string; collapsible?: boolean; collapsed?: boolean; link?: string | { type: "doc"; file: string; label?: string; path?: 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. 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, or an object for more control:

String shorthand
{ type: "category", label: "Configuration", link: "docs/configuration/overview", items: [ "docs/configuration/navigation", "docs/configuration/site", ], }
Object form with custom path
{ type: "category", label: "Documentation", link: { type: "doc", file: "home.md", path: "/", }, items: [ "guides/getting-started", "guides/advanced", ], }

The object form supports these properties:

PropertyTypeDescription
type"doc"Must be "doc"
filestringPath to the markdown file
labelstringOverride the label (defaults to the document title)
pathstringCustom URL path (overrides the default file-based route)

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.

Code
{ "type": "doc", "label": "Overview", "file": "docs/overview" }
TypeScript type declaration
Code
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" }:

Code
{ "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

Custom paths

The path property allows you to customize the URL path for a document. By default, Dev Portal uses the file path to generate the URL, but you can override this behavior by specifying a custom path.

Serving a doc at the root URL
{ type: "doc", file: "home.md", path: "/", label: "Home", }
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 for more details.

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.

Code
{ type: "custom-page", path: "/a-custom-page", element: <MyCustomPage />, display: "always" }
TypeScript type declaration
Code
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 Dev Portal 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.

Code
{ type: "category", label: "Documentation", items: [ "guides/getting-started", "guides/installation", { type: "separator" }, "guides/advanced", "guides/troubleshooting", ], }
TypeScript type declaration
Code
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.

Code
{ 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
Code
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.

Code
{ type: "category", label: "Documentation", items: [ { type: "filter", placeholder: "Filter documentation" }, "guides/getting-started", "guides/installation", "guides/authentication", "guides/deployment", ], }
TypeScript type declaration
Code
type NavigationFilter = { type: "filter"; placeholder?: string; display?: | "auth" | "anon" | "always" | "hide" | ((params: { context: ZudokuContext; auth: UseAuthReturn }) => boolean); };

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
Code
{ "type": "link", "label": "Admin Panel", "to": "/admin", "display": "auth" }

Custom Display Logic

For more complex visibility rules, use a callback function:

Code
{ 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:

Code
{ "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 (e.g., book , code , file-text ).

Code
{ "type": "category", "label": "Getting Started", "icon": "book" }

They can also be set on individual documents in their front matter:

Code
--- 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, Dev Portal 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:

Code
--- 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.

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:

Standalone root doc
navigation: [ { type: "doc", file: "home.md", path: "/", label: "Home", }, ],
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:

Code
navigation: [ { type: "custom-page", path: "/", display: "hide", layout: "none", element: <LandingPage />, }, { 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:

Code
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.

Code
navigationRules: [ { type: "sort", match: "Shipments", by: (a, b) => a.label.localeCompare(b.label), }, ],

For a practical walkthrough with more examples, see the Navigation Rules guide.

Rule Types

Each rule has a type and a match property that targets a navigation item.

TypeDescription
insertAdd items before or after a matched item
modifyChange label, icon, badge, collapsed, collapsible, or display
removeRemove a matched item from the sidebar
sortSort children of a matched category using a custom comparator
moveRelocate 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.

Code
match: "Users/Get User"; // by label match: "Users/0"; // first item match: "Users/-1"; // last item match: "Users/Advanced/0"; // mixed nesting
Edit this page
Last modified on May 29, 2026
DocumentationBranding & Layout
On this page
  • Basic configuration
  • Navigation Items
    • type: link
    • type: category
    • type: doc
    • type: custom-page
    • type: separator
    • type: section
    • type: filter
  • Display Control
    • Custom Display Logic
  • Badges
  • Icons
  • Title & Labels
  • Common Patterns
    • Serving a document at the root URL
    • Landing page with hidden tab
    • Organized sidebar with sections and separators
  • Navigation Rules
    • Rule Types
    • Match Syntax
React
JSON
TypeScript
JSON
TypeScript
React
React
JSON
TypeScript
JSON
React
React
React
TypeScript
React
TypeScript
React
TypeScript
React
TypeScript
JSON
React
JSON
JSON
Markdown
Markdown
React
React
React
React
React
JSON