syntext.json
The syntext.json file is the central configuration for your documentation site. It controls navigation, theming, integrations, and more.
Basic Structure
{
"name": "My Docs",
"description": "Documentation for My Product",
"theme": "default",
"colors": {
"primary": "#6366F1"
},
"navigation": [
{
"group": "Getting Started",
"pages": ["index", "getting-started"]
}
]
}
Required Fields
name
The project name. Displayed in the header and browser tab.
{
"name": "My API Docs"
}
navigation
Defines the sidebar navigation structure. See Navigation for details.
{
"navigation": [
{
"group": "Getting Started",
"pages": ["index", "quickstart"]
}
]
}
Site Metadata
description
Meta description for SEO and social previews.
{
"description": "Complete API documentation for MyProduct"
}
github
Link to your GitHub repository. Shown in the header.
{
"github": "https://github.com/yourorg/yourrepo"
}
socialLinks
Social media links displayed in the header.
{
"socialLinks": [
{ "platform": "GitHub", "url": "https://github.com/yourorg" },
{ "platform": "X", "url": "https://x.com/yourhandle" },
{ "platform": "Discord", "url": "https://discord.gg/yourserver" }
]
}
Supported platforms: GitHub, X, Discord, LinkedIn, YouTube, Slack.
seo.ogImage
Social card image used for link previews (Open Graph and Twitter cards). Shown when your docs are shared on X, Slack, Discord, LinkedIn, and other platforms.
{
"seo": {
"ogImage": "/og-card.png"
}
}
Relative paths resolve against your site URL. Absolute URLs are used as-is. Recommended size: 1200×630 pixels. When set, pages emit og:image, twitter:image, and a summary_large_image Twitter card.
Branding
logo
Logo images for light and dark modes.
{
"logo": {
"light": "/logo-light.svg",
"dark": "/logo-dark.svg"
}
}
Or a single logo:
{
"logo": "/logo.svg"
}
Brand lockup
Two optional keys control how the logo sits beside the site name.
{
"logo": {
"light": "/logo-light.svg",
"dark": "/logo-dark.svg",
"type": "wordmark",
"suffix": "docs"
}
}
| Key | Description |
|---|---|
type |
mark (default) keeps the site name beside the logo. wordmark means the image already contains the name, so the header omits the separate text rather than repeating it |
suffix |
A muted lowercase word after the brand, such as docs. On API reference pages it replaces the uppercase DOCS chip |
type: "wordmark" is ignored when no logo image is configured — otherwise the
header would have nothing to show.
favicon
Favicon configuration.
{
"favicon": "/favicon.ico"
}
Advanced favicon configuration:
{
"favicon": {
"default": "/favicon.ico",
"apple": "/apple-touch-icon.png",
"32": "/favicon-32x32.png",
"16": "/favicon-16x16.png"
}
}
colors
Brand colors.
{
"colors": {
"primary": "#6366F1",
"accent": "#2AC3DE",
"background": "#0F172A"
}
}
| Property | Maps to token | Description |
|---|---|---|
primary |
accent (+ derived hover/subtle/muted ramp) |
Main brand color (links, buttons, active states) |
accent |
accent-strong |
Stronger accent — link hover, emphasis |
background |
bg-page |
Page background |
This is legacy shorthand, kept for compatibility. It is applied after
themeOverrides, so an existing site never changes appearance because a preset
was introduced. For new sites prefer themeOverrides, which
reaches every token rather than three.
Navigation
tabs
Top-level navigation tabs.
{
"tabs": [
{ "name": "Guides", "url": "/guides", "icon": "book" },
{ "name": "API", "url": "/api-reference", "icon": "api" },
{ "name": "SDKs", "url": "/sdks", "icon": "code" }
]
}
anchors
Quick links displayed below the search bar.
{
"anchors": [
{ "name": "API Status", "url": "https://status.example.com", "icon": "signal" },
{ "name": "Support", "url": "mailto:support@example.com", "icon": "help" }
]
}
OpenAPI Integration
openapi
Path or URL to your OpenAPI 3.x specification. At build time, Syntext parses every listed spec and generates one MDX endpoint page per operation plus an overview page per spec — directly into your docs tree, alongside your hand-written pages.
{
"openapi": "./openapi.json"
}
Remote URL (fetched fresh on every build):
{
"openapi": "https://api.example.com/openapi.json"
}
Multiple specs with slug prefixes (microservices, one spec per resource group):
{
"openapi": [
{ "path": "./specs/users.yaml", "prefix": "api-reference/users" },
{ "path": "./specs/orders.yaml", "prefix": "api-reference/orders" },
{ "url": "https://api.example.com/billing.json", "prefix": "api-reference/billing" }
]
}
Multiple base URLs (production + sandbox) for the API playground — declare them once at the top level of syntext.json with baseUrls:
{
"baseUrls": [
{ "url": "https://api.example.com", "description": "Production" },
{ "url": "https://sandbox.example.com", "description": "Sandbox" }
],
"openapi": [
{ "path": "./specs/users.yaml", "prefix": "api-reference/users" }
]
}
Plain strings work too ("baseUrls": ["https://api.example.com"]). If one spec needs different hosts, add a servers array to that openapi entry — it overrides baseUrls for that spec only:
{
"openapi": [
{
"path": "./specs/users.yaml",
"prefix": "api-reference/users",
"servers": [
{ "url": "https://users.example.com", "description": "Production" },
{ "url": "https://users-sandbox.example.com", "description": "Sandbox" }
]
}
]
}
Authorization and extra headers for the API playground can also be declared at the top level — auth sets which header (or query parameter) carries credentials, and headers adds header parameters to every generated endpoint page:
{
"auth": { "name": "Api-Key" },
"headers": [
{ "name": "X-Idempotency-Key", "required": true, "description": "Unique key to safely retry requests" }
],
"openapi": [
{ "path": "./specs/users.yaml", "prefix": "api-reference/users" }
]
}
auth accepts name (the header or query parameter that carries the credential), plus optional label (field label in the playground, defaults to name), in ("header" or "query", defaults to "header"), and prefix (value prefix such as "Bearer ", defaults to none). For a classic Bearer setup: { "name": "Authorization", "label": "Bearer", "prefix": "Bearer " }. headers entries accept name, type, required, and description — plain strings work as shorthand ("headers": ["X-Idempotency-Key"]). Both keys can also be set on an individual openapi entry to override the top level for that spec only.
How generation works:
- Page slugs — with a
prefix, each endpoint page lands at{prefix}/{method}-{path}(e.g.api-reference/users/get-v1-users-user-id) and the spec's overview at{prefix}/overview. Slugs are derived from the HTTP method and path — never fromoperationId— so URLs stay stable even if you rename operations. Without aprefix, the generator's default slugs are used. - Base URLs — when more than one base URL is declared, endpoint pages render a base-URL selector in the API playground, and code snippets plus Try-it requests follow the selected server. Precedence: a
serversarray on anopenapientry > top-levelbaseUrls> the spec's ownserverslist. Top-levelbaseUrlsalso applies to hand-authored REST pages (per-pageserversfrontmatter still wins). - Auth & headers — the playground's Authorization field and extra header parameters follow the same chain: page
authfrontmatter >auth/headerson anopenapientry > top-levelauth/headers> the spec's ownsecuritySchemes. Top-levelauthalso applies to hand-authored REST pages without their ownauthfrontmatter. - Hand-authored pages always win — if a file already exists at a generated page's path, the generated page is skipped. Use this to hand-tune individual endpoint pages while keeping the rest generated.
- Non-fatal errors — a spec that fails to parse or fetch logs a build warning and is skipped; the rest of the build continues.
- Local paths must live inside the repository — paths that resolve outside the repo are rejected.
Because specs are read on every build, committing a spec change and deploying is all it takes to update your API reference — no separate import step. See the OpenAPI sync recipe.
Versioning
versioning
Enable versioned documentation. Each version builds from its own git ref (tag > branch > label); the default version serves at the site root, others under /{label}/. Two or more versions render a switcher in the doc-site header.
{
"versioning": {
"versions": [
{ "label": "v3", "branch": "main", "default": true },
{ "label": "v2", "branch": "v2" },
{ "label": "v1", "branch": "v1", "deprecated": true }
]
}
}
See Versioning for all fields, URL behavior, deprecation banners, and version-scoped search.
Theme Configuration
A theme is data, not code: pick a preset by name, then override individual tokens. Nothing needs to be forked to restyle a site.
theme
Theme preset name.
{
"theme": "default"
}
| Preset | Description |
|---|---|
default |
Syntext's built-in look — Inter + JetBrains Mono, zinc neutrals, blue accent |
gravv |
Bricolage Grotesque display + Geist Mono body, green accent, dark code surfaces in both modes |
An unknown name falls back to default and logs a warning in the build log — it
never fails the build.
themeOverrides
Per-token overrides applied on top of the preset. Every level is optional, so you can change one colour without restating the theme.
{
"theme": "default",
"themeOverrides": {
"fonts": {
"display": "Bricolage Grotesque",
"body": "Geist Mono",
"mono": "Geist Mono"
},
"colors": {
"light": { "accent": "#077155", "bg-page": "#ffffff" },
"dark": { "accent": "#15a07b", "bg-page": "#0b0d10" }
},
"typography": {
"display-h1": { "size": 34, "lineHeight": 42, "letterSpacing": -1.5, "weight": 600 }
},
"radius": { "sm": 8, "md": 12 }
}
}
Fonts take three roles — display (headings), body, and mono. A bare
string sets the family; an object can also set weights and fallback. Families
are fetched from Google Fonts automatically, de-duplicated when one family serves
two roles.
Colours are a light/dark pair of semantic tokens (accent, bg-page,
bg-sidebar, content-primary, border, method-get, syntax-keyword, …).
Syntax colours also drive code highlighting, which is baked at build time rather
than applied with CSS.
Typography covers named styles (display-h1, heading-h2, body-md,
nav-item, eyebrow, code-sm, …). All values are in px, matching Figma.
Unknown token names are ignored with a build warning rather than failing.
See Theming for the full token list.
Precedence
Resolution order, lowest priority first:
- the
defaultpreset - the named preset from
theme themeOverrides- the legacy
colorsblock
colors is applied last on purpose, so an existing site never changes
appearance because a preset or override was introduced.
fonts (deprecated)
Deprecated. Use themeOverrides.fonts instead, which maps to the theme's
display / body / mono roles.
The old block is still honoured by the CLI, which folds it into
themeOverrides.fonts — heading becomes display. An explicit
themeOverrides.fonts always wins.
{
"fonts": { "heading": "Plus Jakarta Sans", "body": "Inter", "mono": "JetBrains Mono" }
}
codeTheme
Not currently applied. Syntax colours come from the theme's syntax-* tokens
(see themeOverrides.colors), because the highlighter bakes colours into the
HTML at build time and cannot be retargeted with CSS afterwards. This key is
accepted but has no effect.
Top Bar
Two keys control the trailing edge of the header.
{
"topbarCta": { "label": "Get API keys", "url": "https://dashboard.example.com/keys" },
"topbarLinks": [
{ "label": "Sign in", "url": "https://dashboard.example.com" }
]
}
| Key | Description |
|---|---|
topbarCta |
The filled call-to-action button. Omit it to keep the default "Get Started" link; set it to null to render no button at all |
topbarLinks |
Plain-text links rendered before the CTA. Each entry needs both label and url; incomplete entries are skipped |
Both apply to guide pages and API reference pages alike.
dashboardUrl is deprecated. It rendered a single link whose label was always
"Dashboard"; use topbarLinks instead, which lets you name it.
Feature Flags
The features block controls what a site contains — which optional controls
exist and which shape a component takes. Theme tokens control how a site is
painted; these control its structure. A design that omits a control can't be
expressed with colour alone.
{
"features": {
"playground": true,
"copyPageActions": { "guides": true, "api": false },
"sidebarMethodBadges": true,
"codeLanguageSelector": "dropdown",
"aiAssistant": true,
"parameterStyle": "table",
"endpointBarAction": "tryIt",
"codeBlockStyle": "pill",
"sidebarStyle": "collapsible",
"panelLabels": "inline",
"copyControlStyle": "icon"
}
}
| Flag | Default | Description |
|---|---|---|
playground |
true |
The "Try it" request playground on API reference pages |
copyPageActions |
true |
The "Copy page" control and its open-in-AI menu. Accepts true/false for both surfaces, or { "guides": true, "api": false } to differ |
sidebarMethodBadges |
true |
HTTP method chips beside API nav items |
codeLanguageSelector |
"dropdown" |
How the request panel offers languages: dropdown or tabs |
codeLanguageTabs |
true |
Deprecated, no-op. Never controlled anything — the panel only ever had a dropdown. Use codeLanguageSelector |
aiAssistant |
true |
Assistant entry points — "Ask Assistant", agent mode |
parameterStyle |
"table" |
Deprecated, no-op. Parameters have only ever rendered as stacked rows; the table variant was never built, so neither value changes anything |
endpointBarAction |
"tryIt" |
Trailing endpoint-bar control: tryIt, copy, or none |
codeBlockStyle |
"pill" |
Code block chrome: pill (floating rounded header) or panel (bordered header rail joined to the block) |
panelLabels |
"inline" |
Where the request/response captions sit: inline (inside the panel header, beside the language selector) or above (a plain caption over the panel, leaving the header to the selector and copy control) |
copyControlStyle |
"icon" |
The panel copy action: icon (a square glyph button) or label (the word "Copy", becoming "Copied" on success) |
shellWidth |
"contained" |
Page shell geometry: contained keeps a large responsive inset each side, full runs edge to edge so the sidebar starts at the viewport edge and columns are divided by rules |
themeToggle |
true |
The light/dark toggle in the top bar. Set false for a design with no theme control — a visitor's stored preference still applies |
responseStyle |
"schema" |
How responses are presented: schema is a status table plus a per-status field tree, prose is a Returns heading carrying the success description from the spec |
endpointBarUrl |
"path" |
Whether the endpoint bar shows just the path, or full to prefix the OpenAPI server host |
navStyle |
"split" |
Top bar rows: split puts the section tabs on a second row below the brand and actions, single pulls them up beside the brand for a one-row header. single also collapses the mobile header to a single bar, keeping the menu button in it rather than in a second breadcrumb row |
sidebarStyle |
"collapsible" |
API sidebar groups: collapsible (<details> disclosure per group) or flat (all items always visible under a plain label) |
Every default reproduces the behaviour that shipped before these flags existed, so a site is unaffected until it opts out.
copyPageActions is per-surface because designs commonly differ — a guide page
carries the control while a dense endpoint page does not:
{ "features": { "copyPageActions": { "guides": true, "api": false } } }
A surface you leave out keeps its default.
aiAssistant is deliberately independent of copyPageActions — the copy menu is
a page action that happens to include AI destinations, whereas aiAssistant is
the assistant itself. You can drop the menu and keep the assistant.
Setting playground: false implicitly drops a tryIt action, since the button
would have nothing to open. Asking for tryIt explicitly while the playground is
off logs a warning and falls back to copy.
Unknown flags and wrong value types are ignored with a build warning.
Custom Code
customCSS
Path to custom CSS file.
{
"customCSS": "/custom.css"
}
scripts
Custom JavaScript files.
{
"scripts": [
{ "src": "https://analytics.example.com/script.js", "async": true },
{ "src": "/custom.js", "defer": true }
]
}
Site Features
banner
Top announcement banner.
{
"banner": {
"text": "🚀 Version 2.0 is now available!",
"link": "/changelog/v2",
"dismissible": true
}
}
search
Search configuration.
{
"search": {
"enabled": true,
"placeholder": "Search docs..."
}
}
ai
AI assistant configuration.
{
"ai": {
"enabled": true,
"placeholder": "Ask a question..."
}
}
Redirects
redirects
URL redirect rules.
{
"redirects": [
{ "from": "/old-path", "to": "/new-path" },
{ "from": "/moved", "to": "/new-home", "status": 302 },
{ "from": "/docs/*", "to": "/:splat" }
]
}
Each entry takes:
| Field | Type | Description |
|---|---|---|
from |
string |
Path to redirect from. Normalized to a leading slash, no trailing slash |
to |
string |
Target — a site path or an absolute https:// URL |
status |
number |
HTTP status code (optional, defaults to 301) |
Mintlify-style { "source", "destination" } keys are accepted as aliases for from / to.
For each literal redirect (no * or :param patterns), the build also generates a small noindex stub page at the old path so legacy URLs resolve on the live site. Real pages always win — a redirect whose from collides with an existing page is skipped, and stub pages are excluded from the sitemap.
Wildcard and :param patterns are recorded in the generated _redirects file but are not currently applied by the doc-site edge — only literal redirects (which get stub pages) resolve on the live site. If a legacy URL must keep working, list it as a literal from/to entry.
_redirects.json file
In addition to the redirects key, the build looks for a standalone _redirects.json file at your docs root or repo root (docs root is checked first). It contains the same entry shape as a plain JSON array:
[
{ "from": "/old-path", "to": "/new-path" },
{ "source": "/legacy/quickstart", "destination": "/getting-started" }
]
This is handy when migrating from a platform that exports redirects as a file — stx migrate writes one automatically.
Merge behavior:
- Entries from
syntext.jsonand_redirects.jsonare combined into one list - On conflicting
frompaths, thesyntext.jsonentry wins - If
_redirects.jsonis not valid JSON (or not a JSON array), the build logs a warning and ignores the file — the build still succeeds - Entries missing
fromortoare silently dropped
Footer
Every doc site renders a full-width footer band at the bottom of each page. By default it shows your social links (from socialLinks and github) alongside a "Powered by Syntext" credit.
footer
Add footer.columns to upgrade to a rich footer with your logo, social icons, and grouped link columns — similar to what you'd find on a marketing site.
{
"footer": {
"columns": [
{
"title": "Product",
"links": [
{ "label": "Documentation", "url": "/guides/quickstart" },
{ "label": "API Reference", "url": "/api-reference/overview" }
]
},
{
"title": "Legal",
"links": [
{ "label": "Privacy Policy", "url": "https://example.com/privacy" },
{ "label": "Terms of Service", "url": "https://example.com/terms" }
]
}
]
}
}
Each column takes:
| Field | Type | Description |
|---|---|---|
title |
string |
Column heading (optional) |
links |
array |
Links in the column — each needs a label and a url |
Behavior:
- Full-bleed — the footer spans the entire viewport width, outside the content grid, on both guide and API reference pages
- Relative URLs (
/guides/...) navigate within your doc site; absolute URLs (https://...) open in a new tab - The brand column shows your
logo(light/dark aware), project name, and social icons automatically - Columns with no valid links are skipped; omit
footerentirely to keep the compact default footer
Complete Example
{
"name": "Acme API",
"description": "Complete API documentation for Acme Platform",
"theme": "default",
"logo": {
"light": "/logo-light.svg",
"dark": "/logo-dark.svg"
},
"favicon": "/favicon.ico",
"themeOverrides": {
"fonts": { "display": "Bricolage Grotesque", "body": "Inter" },
"colors": {
"light": { "accent": "#6366F1" },
"dark": { "accent": "#818CF8" }
},
"radius": { "sm": 6, "md": 10 }
},
"features": {
"playground": true,
"parameterStyle": "table",
"endpointBarAction": "tryIt"
},
"github": "https://github.com/acme/api",
"socialLinks": [
{ "platform": "GitHub", "url": "https://github.com/acme" },
{ "platform": "Discord", "url": "https://discord.gg/acme" }
],
"tabs": [
{ "name": "Guides", "url": "/guides", "icon": "book" },
{ "name": "API Reference", "url": "/api-reference", "icon": "api" }
],
"navigation": [
{
"group": "Getting Started",
"pages": ["index", "quickstart", "authentication"]
},
{
"group": "Guides",
"pages": ["guides/webhooks", "guides/pagination", "guides/errors"]
}
],
"openapi": "./openapi.json",
"baseUrls": [
{ "url": "https://api.acme.com", "description": "Production" },
{ "url": "https://sandbox.acme.com", "description": "Sandbox" }
],
"ai": { "enabled": true },
"footer": {
"columns": [
{
"title": "Product",
"links": [
{ "label": "Documentation", "url": "/guides/quickstart" },
{ "label": "Pricing", "url": "https://acme.com/pricing" }
]
},
{
"title": "Company",
"links": [
{ "label": "About", "url": "https://acme.com/about" },
{ "label": "Blog", "url": "https://acme.com/blog" }
]
}
]
}
}