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"
}

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"
}

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

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 from operationId — so URLs stay stable even if you rename operations. Without a prefix, 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 servers array on an openapi entry > top-level baseUrls > the spec's own servers list. Top-level baseUrls also applies to hand-authored REST pages (per-page servers frontmatter still wins).
  • Auth & headers — the playground's Authorization field and extra header parameters follow the same chain: page auth frontmatter > auth/headers on an openapi entry > top-level auth/headers > the spec's own securitySchemes. Top-level auth also applies to hand-authored REST pages without their own auth frontmatter.
  • 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:

  1. the default preset
  2. the named preset from theme
  3. themeOverrides
  4. the legacy colors block

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

Top announcement banner.

{
  "banner": {
    "text": "🚀 Version 2.0 is now available!",
    "link": "/changelog/v2",
    "dismissible": true
  }
}

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.json and _redirects.json are combined into one list
  • On conflicting from paths, the syntext.json entry wins
  • If _redirects.json is not valid JSON (or not a JSON array), the build logs a warning and ignores the file — the build still succeeds
  • Entries missing from or to are silently dropped

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.

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 footer entirely 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" }
        ]
      }
    ]
  }
}
Was this page helpful?