docs.json Reference
Complete reference for every field in docs.json: themes, colors, navigation, tabs, OpenAPI integration, branding, SEO, analytics, and AI chat settings.
The docs.json file is the central configuration for your Jamdesk documentation site.
Key settings from your docs.json are displayed in the Dashboard under Project Settings → Configuration Highlights. This view is read-only and updates automatically after each successful build.
Required Fields
name
Type: string (required)
Your documentation site's name. Displayed in the header and browser tab. To change the tab title format, see seo.titleTemplate.
{ "name": "Acme API Docs" }
theme
Type: "jam" | "nebula" | "pulsar" | "halo" | "dusk" (required)
Clean, modern design with Inter font. Header-based navigation.
Best for: Most documentation sites, API references
colors
Type: object (required)
| Field | Type | Required | Description |
|---|---|---|---|
primary | string (hex) | Yes | Main brand color |
light | string (hex) | No | Light theme accent |
dark | string (hex) | No | Dark theme accent |
{
"colors": {
"primary": "#635BFF",
"light": "#7C75FF",
"dark": "#4F46E5"
}
}
Branding
favicon
Type: string or object
Path to your favicon file (SVG recommended). Provide one image, or separate light / dark variants.
| Field | Type | Description |
|---|---|---|
light | string | Favicon for light mode (required when using the object form) |
dark | string | Favicon for dark mode (optional, falls back to light) |
{ "favicon": "/images/favicon.svg" }
{
"favicon": {
"light": "/images/favicon.svg",
"dark": "/images/favicon-dark.svg"
}
}
logo
Type: object
| Field | Type | Description |
|---|---|---|
light | string | Logo for light mode |
dark | string | Logo for dark mode |
href | string | URL when logo is clicked |
{
"logo": {
"light": "/images/logo-light.webp",
"dark": "/images/logo-dark.webp",
"href": "https://yoursite.com"
}
}
Typography
fonts
Type: object (optional)
Override the theme's default font for body text and headings. Each theme ships with a tuned default. Only set fonts when you need a different look.
Use the same font everywhere:
{
"fonts": {
"family": "Lora"
}
}
Split heading and body:
{
"fonts": {
"heading": { "family": "Space Grotesk" },
"body": { "family": "Inter" }
}
}
| Field | Type | Description |
|---|---|---|
family | string | Font family name. Any Google Font works. The build fetches it automatically |
weight | number | Single weight to load (e.g. 400). Omit to load 400, 500, 600, 700 |
source | string | URL or /-relative path to a self-hosted font file. Skips Google Fonts |
format | "woff" | "woff2" | Required when source is set |
heading and body take the same fields. See Theming → Typography for guidance on picking fonts.
Appearance
appearance
Type: object (optional)
Control the default dark-mode behavior of your site.
{
"appearance": {
"default": "dark",
"strict": true
}
}
| Field | Type | Default | Description |
|---|---|---|---|
default | "system" | "light" | "dark" | "system" | Initial mode for first-time visitors |
strict | boolean | false | When true, hides the navbar toggle so visitors stay on default |
See Theming → Dark Mode for how the toggle behaves.
Page Metadata
metadata
Type: object (optional)
Control the page metadata shown on every documentation page.
{
"metadata": {
"timestamp": true
}
}
| Field | Type | Default | Description |
|---|---|---|---|
timestamp | boolean | false | When true, shows a "Last updated on June 15, 2026"-style line in the footer of every page. The date comes from the last Git commit that changed that page. |
The date renders on your published site and in jamdesk dev. Pages you haven't edited keep their original date.
SEO
seo.titleTemplate
Type: string (optional)
Default: %s — <name>, where <name> is your name
Set the format of the page title in the browser tab and in search results. Put %s where the page title goes. Every %s is replaced. The template can be up to 200 characters. A template with no %s fails the build. jamdesk validate shows the error.
{
"seo": {
"titleTemplate": "%s | Acme"
}
}
A page titled Getting started then shows Getting started | Acme. Use "%s" alone to leave the site name out. This does not change og:title or twitter:title. See Page Titles for pages without a title.
seo.socialCard
Type: object (optional)
Change the social card Jamdesk generates for each page. Without this key, cards keep their default look.
{
"seo": {
"socialCard": {
"theme": "dark",
"brandColor": true
}
}
}
| Field | Type | Default | Description |
|---|---|---|---|
theme | "light" | "dark" | "light" | "dark" uses light text on a dark background and your logo.dark image. |
brandColor | boolean | false | Color the bottom bar and the section badge with colors.primary (colors.light on a dark card). |
accentBar | boolean | true | false removes the bar along the bottom of the card. |
siteName | boolean | true | false hides the site name under the logo. The name stays when the card shows your favicon in place of the logo. |
customImage | "background" | "replace" | "background" | "replace" uses your og:image as the whole card, with no text on it. |
The card can only draw SVG, PNG and JPG logos. Jamdesk converts PNG and JPG logo files to WebP when it builds your site. Use an SVG logo to show your full logo on the card. The card can't draw text inside an SVG logo. Convert any text to outlines (paths) in your design tool and export the SVG again.
See Customize the social card for what each option does and which image a page uses.
Localization
localization
Type: object (optional)
Control how visitors reach the translated version of your docs.
{
"localization": {
"autoRedirect": true
}
}
| Field | Type | Default | Description |
|---|---|---|---|
autoRedirect | boolean | false | When true, a first-time visitor arriving at your docs root is sent to the language matching their browser's Accept-Language header. Requires two or more entries in navigation.languages. |
Only roots redirect. A deep link such as /guides/authentication always serves the page it names. A link you paste into a ticket opens the same page for everyone. Search engine crawlers are never redirected. Your hreflang tags keep deciding what gets indexed.
The visitor's language is remembered for a year. Choosing a language from the language picker overrides the automatic match from then on.
See Languages → Automatic language routing for the full behavior.
Banner
banner
Show a site-wide announcement bar pinned to the top of every page, above the header, at full width, in your theme's accent color. Use it for launches, migrations, maintenance windows, or any message every visitor should see.
{
"banner": {
"content": "🎉 Version 2.0 is live! Read the [changelog](/changelog).",
"dismissible": true
}
}
| Field | Type | Default | Description |
|---|---|---|---|
content | string | - | Required. The banner text. Supports basic inline formatting: links [text](url), bold (**text**), and italic (*text*). Custom MDX components are not supported. |
dismissible | boolean | false | When true, shows a close button. A dismissed banner stays hidden for that visitor until you change content. |
The banner renders on your published site and in jamdesk dev. It is configured globally (one banner for the whole site). Per-tab and per-language banners are not available yet.
OpenAPI
api.openapi
Type: string | string[]
List the OpenAPI 3.x spec files you want Jamdesk to validate and use for endpoint pages. Use paths relative to your docs.json.
{
"api": {
"openapi": ["/openapi/api.yaml"]
}
}Generate an endpoint page by adding an openapi field to its frontmatter:
---
title: Create Ticket
openapi: /openapi/api.yaml POST /tickets
---
If you only have one spec listed, you can also use the short format:
---
title: Create Ticket
openapi: POST /tickets
---
See OpenAPI Example for a live endpoint page and Directory Structure for file placement.
If your site is multilingual, drop a <spec>.<lang>.<ext> file next to each source spec (e.g., openapi/api.fr.yaml) and Jamdesk serves it on that language's URLs. See Translating OpenAPI Specs.
An asyncapi key is accepted anywhere openapi is. Jamdesk does not render AsyncAPI specs yet. Nothing is generated from it. jamdesk validate warns when it finds one, before you build the site.
navigation openapi (generated pages)
Type: object
Put an openapi object on a navigation tab and set generate: true. Jamdesk builds one endpoint page per operation in that spec at build time, plus the sidebar groups to hold them. You author and commit nothing.
{
"navigation": {
"tabs": [
{
"tab": "API Reference",
"openapi": { "source": "/openapi/api.yaml", "generate": true }
}
]
}
}| Key | Type | Description |
|---|---|---|
source | string | Path to the spec, relative to your docs.json |
generate | boolean | true builds the pages and the sidebar. Without it the key stays inert |
Generated pages live under the tab's own name, slugified, with a slug built from the method and path. The tab above puts POST /tickets at /api-reference/post-tickets. Operations are grouped by their first tag. Specs with no tags fall back to the first meaningful path segment: /api/v1/auctions/{auctionId} lands under Auctions. Titles come from the operation summary when the spec has one, otherwise from the method and path. An operation that addresses one resource gets a singular title (GET /users/{id} → "Get User").
generate is opt-in. A bare "openapi": "/openapi/api.yaml" on a tab, or an object without generate: true, keeps working as before. No existing config gains a hundred pages on its next build.
A committed .mdx file always wins a slug collision. Adopt generation gradually: turn it on, then delete your hand-written endpoint pages one at a time.
Rename a path in your spec and the generated slug moves with it. Jamdesk keeps a per-operation slug history. On each build it emits a redirect from every former URL to the current one. Inbound links and bookmarks survive the rename. Your own redirects and any live page still take precedence.
Current limits: generation runs on top-level navigation.tabs only, not on groups, anchors, or tabs nested under languages or versions. It produces pages for your default language only. A directory key is accepted by the schema but has no effect on where pages land.
api.mdx.server
Type: string
Base URL used in code examples on api: frontmatter pages (the MDX-authored kind). openapi: pages take their servers from the spec.
{
"api": {
"mdx": {
"server": "https://api.example.com"
}
}
}
An array is accepted for compatibility. Only the first entry is used. The rest are discarded. jamdesk validate warns when you list more than one.
api.examples.languages
Type: string[]
Default: ["curl", "python", "javascript"]
Choose which programming languages appear in auto-generated API code examples on openapi: pages. The array order sets the tab order. The first language is selected by default.
Supported values: curl, bash, python, javascript, go, ruby, csharp, java, rust, php
bash is an alias for curl and produces the same output.{
"api": {
"examples": {
"languages": ["curl", "python", "javascript", "go", "ruby", "csharp", "java", "rust", "php"]
}
}
}{
"api": {
"examples": {
"languages": ["python", "javascript", "go"]
}
}
}api.examples.defaults
Type: "required" | "all"
Default: "all"
Control which parameters appear in auto-generated code examples.
| Value | Behavior |
|---|---|
"all" | Examples include all parameters with placeholder values |
"required" | Examples only include parameters marked as required in the spec |
{
"api": {
"examples": {
"defaults": "required"
}
}
}
api.examples.prefill
Type: boolean
Default: false
When true, the API Playground pre-fills parameter fields with example values from your OpenAPI spec.
{
"api": {
"examples": {
"prefill": true
}
}
}
api.playground.display
Type: "interactive" | "simple" | "none"
Default: "interactive"
Controls the API Playground on endpoint pages. A "Try it" button appears on every openapi: and api: page by default.
| Value | Behavior |
|---|---|
"interactive" | Full playground: fill params, generate code, send requests (default) |
"simple" | Fill params and copy code, but no Send button |
"none" | Playground disabled |
{
"api": {
"playground": {
"display": "interactive"
}
}
}
See API Playground for usage details and per-page overrides.
api.mdx.auth.method
Type: "bearer" | "basic" | "key" | "cobo"
Authentication method used in auto-generated code examples. Examples then include the matching authentication header.
| Value | Header format |
|---|---|
"bearer" | Authorization: Bearer <token> |
"basic" | Authorization: Basic <base64> |
"key" | Custom header (see api.mdx.auth.name) |
"cobo" | Cobo-specific authentication |
{
"api": {
"mdx": {
"auth": {
"method": "bearer"
}
}
}
}
api.mdx.auth.name
Type: string
Custom header name for key-based authentication. Only used when api.mdx.auth.method is "key".
{
"api": {
"mdx": {
"auth": {
"method": "key",
"name": "X-API-Key"
}
}
}
}
Navigation
tabsPosition
Type: "top" | "left"
Controls where navigation tabs are displayed.
| Value | Description |
|---|---|
"top" | Tabs appear in the header tab bar |
"left" | Tabs appear at the top of the sidebar |
Default value depends on your theme:
| Theme | Default |
|---|---|
| jam | "left" |
| nebula | "left" |
| pulsar | "top" |
| halo | "left" |
| dusk | "left" |
{ "tabsPosition": "left" }
tabsAlignment
Type: "start" | "center" | "end"
Sets the horizontal alignment of the tab row when tabs are in the header (tabsPosition: "top"). It has no effect with tabsPosition: "left". The Pulsar theme ignores it and places top tabs on the right side of the header.
| Value | Description |
|---|---|
"start" | Tabs sit at the left of the header (default) |
"center" | Tabs are centered in the header |
"end" | Tabs sit at the right of the header |
{ "tabsPosition": "top", "tabsAlignment": "center" }
anchors
Type: array
External links that appear at the top of the sidebar on all pages.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display text |
href | string | Yes | URL (external link) |
icon | string | No | Font Awesome icon name |
{
"anchors": [
{ "name": "Blog", "href": "https://blog.example.com", "icon": "newspaper" }
]
}
navigation (structure)
Type: object
The navigation structure for your documentation. See Navigation for detailed documentation.
Pages can be strings (title auto-generated from filename) or objects with a custom title:
"pages": [
"introduction",
{ "page": "content/mdx-basics", "title": "MDX Basics" }
]
{
"navigation": {
"tabs": [
{
"tab": "Docs",
"icon": "book-open",
"groups": [
{
"group": "Getting Started",
"pages": ["introduction", "quickstart"]
}
]
}
]
}
}Navbar & Footer
navbar
Type: object
| Field | Type | Description |
|---|---|---|
links | array | Navigation links |
links[].label | string | Default button text |
links[].labels | object | Optional per-language overrides keyed by language code (e.g. fr, es). Falls back to label |
links[].icon | icon | Optional icon to display next to the label |
links[].href | string | Destination URL |
primary | object | Primary CTA button |
primary.label | string | Default button text |
primary.labels | object | Optional per-language overrides keyed by language code. Falls back to label |
{
"navbar": {
"links": [
{
"label": "Blog",
"labels": { "fr": "Blog", "es": "Blog" },
"href": "/blog"
},
{
"label": "Pricing",
"labels": { "fr": "Tarifs", "es": "Precios" },
"href": "/pricing"
}
],
"primary": {
"type": "button",
"label": "Dashboard",
"labels": { "fr": "Tableau de bord", "es": "Panel" },
"href": "https://app.example.com"
}
}
}
labels is optional. Single-language docs can omit it. When set, the current URL language (e.g. /fr/...) selects the matching override.
footer
Type: object
Configure the page footer with social links and custom link columns.
{
"footer": {
"socials": {
"github": "https://github.com/yourorg",
"x": "https://x.com/yourhandle",
"discord": "https://discord.gg/yourserver"
},
"links": [
{
"header": "Resources",
"items": [
{ "label": "Blog", "href": "https://example.com/blog" },
{ "label": "Changelog", "href": "/changelog" }
]
}
]
}
}
| Field | Type | Description |
|---|---|---|
socials | object | Social media platform URLs |
links | array | Link column configurations |
links[].header | string | Column heading |
links[].items | array | Array of { label, href } objects |
Supported social platforms: github, x, twitter, linkedin, discord, slack, youtube, instagram, facebook, reddit, telegram, bluesky, threads, medium, hacker-news, website
Styling
styling.latex
Type: boolean
Enable LaTeX math rendering with KaTeX. Then use $...$ for inline math and $$...$$ for block equations.
{
"styling": {
"latex": true
}
}
See Math & LaTeX for usage details.
styling.js
Type: string | string[]
Custom JavaScript file(s) to include on every page. Paths are relative to your docs directory and must start with /.
{
"styling": {
"js": "/script.js"
}
}
Pass an array for multiple files:
{
"styling": {
"js": ["/chat.js", "/analytics.js"]
}
}
Without this field, Jamdesk auto-detects .js files in your project root. See Custom JavaScript for details.
Search
search
Type: object (optional)
Customize the documentation search bar. Search works without it. Set it to change the placeholder text or list popular pages in the empty state.
| Field | Type | Default | Description |
|---|---|---|---|
prompt | string | Search documentation… | Placeholder text shown in the search input |
popularPages | array | Quick Start, Introduction | Quick-access links shown before the visitor types a query |
{
"search": {
"prompt": "Ask me anything…",
"popularPages": [
{ "title": "Quick Start", "slug": "quickstart", "icon": "rocket" },
{ "title": "Authentication", "slug": "guides/authentication", "icon": "key" }
]
}
}
Popular pages
Each entry in popularPages accepts:
| Field | Type | Required | Description |
|---|---|---|---|
title | string | Yes | Label shown for the link |
slug | string | Yes | Page path, without a leading slash or .mdx extension (for example quickstart, or guides/authentication for the file guides/authentication.mdx) |
icon | string | No | Font Awesome icon name shown beside the link (for example rocket or bell) |
The icon field also accepts the full { "name", "style", "library" } object. See Icon object form. Without popularPages, Jamdesk shows Quick Start and Introduction.
Chat
chat
Type: object (optional)
Configure the built-in AI chat assistant. Chat is on by default. Set this field to change starter questions, the button name and icon, or to turn chat off.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Set false to remove the chat panel from your site |
starterQuestions | string[] | auto-generated | Up to 4 questions shown when chat opens (5-200 chars each). Auto-generated during builds when omitted. Set to [] for none |
label | string | "Ask AI" in the page's language | Replaces "Ask AI" on the chat button, panel header and search shortcut. Up to 40 characters. Applies to every language. |
icon | string | sparkles | A Font Awesome icon name, an image URL, or a path to an image in your project. A Font Awesome icon follows your accent color. An image is shown as is. Pick one that reads on light and dark backgrounds. |
{
"chat": {
"label": "Acme Assistant",
"icon": "/images/assistant.svg",
"starterQuestions": [
"How do I get started?",
"What API endpoints are available?"
]
}
}
See AI Chat for details on how chat works and what visitors see.
AI Actions Menu
contextual
Type: object (optional)
Configure the AI Actions dropdown that appears on every page. It is on by default with every option. Set this field to pick which options appear or to turn the menu off.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Set false to remove the AI Actions menu from your site |
options | array | all built-in | List of option keys and/or custom option objects |
Built-in option keys: copy, view, chatgpt, claude, perplexity, gemini, mcp, cursor, vscode
{
"contextual": {
"options": ["copy", "claude", "mcp", "cursor"]
}
}
Add custom options alongside built-in ones:
{
"contextual": {
"options": [
"copy",
"claude",
{
"title": "Ask on Discord",
"description": "Get help from the community",
"icon": "discord",
"href": "https://discord.gg/your-server"
}
]
}
}
See AI Actions Menu for the full list of options and custom option format.
Spellcheck
spellcheck
Type: object (optional)
Configure the jamdesk spellcheck CLI command. Set it to add project-specific words to the ignore list.
| Field | Type | Description |
|---|---|---|
ignore | string[] | Words to skip during spellcheck (product names, technical terms, etc.) |
{
"spellcheck": {
"ignore": ["Acme", "kubectl", "Terraform"]
}
}
The CLI includes 180+ built-in tech terms (API, GraphQL, Kubernetes, React, etc.). It also ignores your project name from the name field. Only add words specific to your project.
See CLI Overview: Spellcheck for usage details and interactive fix mode.
Images
images.convertToWebp
Type: boolean (optional, default false)
Enable automatic WebP conversion for PNG and JPG assets during builds. Converted files are usually 60-80% smaller than the originals with no visible quality loss. Jamdesk rewrites references in your MDX, custom CSS, custom JS, and docs.json. You don't change any paths.
Favicons, og:image, and twitter:image stay in their original format. Not every social crawler or email client renders WebP reliably. A broken preview card is worse than a slightly larger JPG.
{
"images": {
"convertToWebp": true
}
}
See Automatic Image Conversion for what gets converted, how caching works, and the build progress indicator.
Access Control
auth.password
Type: object (optional)
Turn on shared-password protection for your site. This file only declares the config. Set the passphrase in the dashboard after the next build runs.
Set auth.password.enabled: true to lock the whole site, or list paths under auth.password.private[] to protect only those pages. Either one triggers the same dashboard password prompt on the next build.
{
"auth": {
"password": {
"enabled": true,
"hint": "Ask your account manager",
"public": ["/marketing/**", "/changelog"]
}
}
}
| Field | Type | Description |
|---|---|---|
enabled | boolean | Whole-site mode. When true, every page needs the password (except anything marked public). |
hint | string (max 200 chars) | Plain-text hint shown on the unlock screen. No HTML. |
public | string[] | Path globs that bypass the password. Supports * (one segment) and ** (recursive). A bare / is rejected. |
private | string[] | Exact paths that require the password. Setting this without enabled activates specific-pages mode. |
See Password Protection for the full walkthrough, including the dashboard flow and how frontmatter public: true / private: true interact with these arrays.
auth.jwt
Type: object (optional)
Gate the whole site behind your own login system. Your backend signs a short-lived token for each signed-in visitor. Jamdesk exchanges it for a session cookie. The signing key is generated in the dashboard, not in this file. You can't enable auth.jwt and auth.password together. A build with both on fails with a config_error.
{
"auth": {
"jwt": {
"enabled": true,
"loginUrl": "https://app.example.com/docs-login",
"public": ["/changelog/**", "/status"]
}
}
}
| Field | Type | Description |
|---|---|---|
enabled | boolean | Turn JWT authentication on. Every page needs a session except anything marked public. |
loginUrl | string | Required when enabled is true. An absolute https:// URL on your side. Visitors without a session are sent here with ?redirect=<path> so you can bring them back to the page they asked for. |
public | string[] | Path globs that stay reachable without signing in. Same syntax as auth.password.public, and merged with frontmatter public: true and "public": true on navigation groups. |
Per-page access on top of this comes from groups in page frontmatter. See JWT Authentication for the token format, the redirect flow, and how groups work.
Full Example
{
"$schema": "https://jamdesk.com/docs.json",
"name": "Acme Documentation",
"description": "Learn how to use Acme",
"theme": "jam",
"colors": {
"primary": "#635BFF"
},
"favicon": "/images/favicon.svg",
"logo": {
"light": "/images/logo-light.webp",
"dark": "/images/logo-dark.webp"
},
"api": {
"openapi": ["/openapi/api.yaml"],
"playground": {
"display": "interactive"
},
"examples": {
"languages": ["curl", "python", "javascript"],
"prefill": true
}
},
"styling": {
"latex": true,
"js": "/script.js"
},
"chat": {
"starterQuestions": ["How do I get started?", "What endpoints are available?"]
},
"contextual": {
"options": ["copy", "claude", "chatgpt", "mcp", "cursor"]
},
"spellcheck": {
"ignore": ["Acme"]
},
"anchors": [
{ "name": "Blog", "href": "https://blog.acme.com", "icon": "newspaper" }
],
"navbar": {
"links": [
{ "label": "Support", "href": "/support" }
],
"primary": {
"type": "button",
"label": "Dashboard",
"href": "https://app.acme.com"
}
},
"navigation": {
"tabs": [
{
"tab": "Docs",
"icon": "book-open",
"groups": [
{
"group": "Get Started",
"pages": ["introduction", "quickstart"]
}
]
},
{
"tab": "API Reference",
"icon": "code",
"groups": [
{
"group": "Endpoints",
"pages": ["api/users", "api/posts"]
}
]
}
]
}
}