Configuration

Every site config key, what it defaults to, and what it does.

On this page

Configuration

Every project has one site config at its root - bxsites.yaml (or .yml), the default/preferred format, or bxsites.json for a project that wants to stay on it. Both are fully supported and produce the exact same result; bxSites new scaffolds bxsites.yaml unless --format=json is passed (see Getting Started). If a project somehow has more than one, bxsites.yaml wins, then bxsites.yml, then bxsites.json.

name: "My Docs"
description: ""
baseURL: "/"
theme:
  name: bootstrap
  options: {}
  logo: ""
  favicon: ""
search: true
searchProvider:
  provider: local
  algolia: { appId: "", apiKey: "", indexName: "", insights: false }
nav: []
markdown:
  enableAdmonition: true
repo:
  url: ""
  editUri: ""
social: []
footer: false
lastUpdated: false
mermaid: false
math: false
analytics:
  provider: ""
  id: ""
ogImage: ""
generateOgImages: false
extraCss: []
extraJs: []
assets:
  fingerprint: true
  bundle: true
  images: { enabled: true, widths: [400, 800, 1200, 1600], formats: [original, webp] }
plugins: []
i18n:
  defaultLocale: { code: en, label: English }
  locales: []
blog:
  postsPerPage: 10
  feed: true

The equivalent bxsites.json, for a project that prefers it:

{
	"name": "My Docs",
	"description": "",
	"baseURL": "/",
	"theme": {
		"name": "bootstrap",
		"options": {},
		"logo": "",
		"favicon": ""
	},
	"search": true,
	"searchProvider": {
		"provider": "local",
		"algolia": { "appId": "", "apiKey": "", "indexName": "", "insights": false }
	},
	"nav": [],
	"markdown": { "enableAdmonition": true },
	"repo": {
		"url": "",
		"editUri": ""
	},
	"social": [],
	"footer": false,
	"lastUpdated": false,
	"mermaid": false,
	"math": false,
	"analytics": {
		"provider": "",
		"id": ""
	},
	"ogImage": "",
	"generateOgImages": false,
	"extraCss": [],
	"extraJs": [],
	"assets": {
		"fingerprint": true,
		"bundle": true,
		"images": { "enabled": true, "widths": [400, 800, 1200, 1600], "formats": ["original", "webp"] }
	},
	"plugins": [],
	"i18n": {
		"defaultLocale": { "code": "en", "label": "English" },
		"locales": []
	},
	"blog": {
		"postsPerPage": 10,
		"feed": true
	}
}

Only name is required - everything else falls back to the defaults shown above. A partial theme object is merged one level deep, so {theme: {name: material}} alone still keeps the default (empty) options. Every key below is named/shaped identically in both formats - the rest of this page just shows JSON snippets for brevity, but every one of them reads the same in YAML.

name

The site name, shown in the header/brand mark and page titles. Required.

description

An optional site description, used as the fallback <meta name="description"> and og:description for any page that doesn't set its own description frontmatter (see Getting Started).

baseURL

Controls how every internal link, asset path and nav entry is prefixed, and doubles as the site's canonical URL for sitemap.xml and llms.txt.

  • Left blank or "/" (the default) - links stay root-relative (/page/), and neither sitemap.xml nor an absolute-URL llms.txt is generated (there's no canonical domain to build them from).
  • A bare path, e.g. "my-docs" or "/my-docs/" - the site is assumed to be served from that sub-path, and every internal link, nav entry and asset is prefixed with it (/my-docs/page/). Still no sitemap.xml, since there's still no absolute domain.
  • A full URL, e.g. "https://docs.example.com/" - the path portion (/ here) is used the same way a bare path would be, and sitemap.xml is written at build time with every non-hidden page's absolute URL under that domain.

llms.txt (see below) is always written; it just prefers an absolute URL when baseURL provides one.

llms.txt

Every build writes a llms.txt to the site root - a plain Markdown index of every non-hidden page, following the emerging llms.txt convention for helping LLM-based tools navigate a site without crawling its rendered HTML. There's no config key for this; it's generated automatically, using an absolute URL per link when baseURL is a full URL, or a basePath-relative one otherwise.

sitemap.xml

Written at the site root, but only when baseURL is a full URL (see above) - a sitemap needs an absolute domain to be meaningful. Lists every non-hidden page per the sitemaps.org protocol.

theme

  • theme.name - one of the built-in themes (bootstrap, material, tailwind), or the name of a custom theme you provide via a theme/ folder at the project root (see Themes)
  • theme.logo - path/URL to an image shown next to the site name in the header brand mark (in place of the default "âš¡" glyph) - a relative path (e.g. "assets/logo.svg", resolved against docs/assets/) is prefixed with baseURL like any other internal asset; an absolute URL is used as-is. Left blank (the default), the header shows "âš¡ <site name>".
  • theme.favicon - path/URL to a favicon, resolved the same way as theme.logo. Left blank (the default), no <link rel="icon"> is rendered at all (falling back to the browser's own default behavior).
  • theme.options - theme-specific options, read by every built-in theme:
    • theme.options.colorMode - "auto" (the default), "light" or "dark". Controls which mode a first-time visitor sees before they've picked one themselves via the header's dark/light toggle - "auto" follows their OS preference, "light"/"dark" pins a fixed default. Once a visitor toggles the switch, their own choice (stored in localStorage) always wins on later visits, regardless of this setting.

      { "theme": { "options": { "colorMode": "dark" } } }
      
    • theme.options.navCollapsible - false (the default) renders every nav section always expanded, as today. true gives every section with children a toggle button the visitor can click to collapse/expand it - whether that section is a bare group heading (a folder with no index.md) or links to its own page. The section containing the page you're currently on always starts open, regardless of navExpandAll, so navigating there never buries the very link you're on.

    • theme.options.navExpandAll - only relevant when navCollapsible is true. true (the default) starts every section open; false starts every section collapsed, except the one containing the current page.

      { "theme": { "options": { "navCollapsible": true, "navExpandAll": false } } }
      
    • theme.options.tocPosition - where a page's own "On this page" table of contents renders. "top" (the default) renders it inline, at the top of the article, same as today. "sticky" moves it into its own right-hand column that stays in view while the article scrolls underneath it - the same "On this page" list, just pinned, which helps on long pages. The pinned column only fits on wide viewports (it's hidden below the point a 3-column layout would get cramped); below that width sticky mode instead renders a collapsible "On this page" bar pinned to the top of the viewport while scrolling - tap to expand the list, same treatment VitePress/GitBook use on mobile - so the TOC stays reachable at every viewport width, it just changes shape depending on how much room there is.

      { "theme": { "options": { "tocPosition": "sticky" } } }
      
    • theme.options.pageMetaPosition - where the edit-this-page/download- markdown/last-updated row renders relative to a page's own content. "bottom" (the default) renders it as a small footer note right before the article ends. "top" renders it up near the title instead, the same place it always rendered before this option existed.

      { "theme": { "options": { "pageMetaPosition": "top" } } }
      

true (the default) builds a static search index and wires up the search box; false skips both entirely - no search-index.json, no search UI, no extra JS shipped. See Search.

searchProvider

Which search UI search: true wires up:

  • provider - "local" (the default) is bx-sites' own static/client-side search (search-index.json + lunr.js, see Search). "algolia" wires up Algolia DocSearch instead, and "pagefind" wires up Pagefind. Any other value is a project's own custom provider, wired up by a theme/ override - see Search.
  • algolia - required when provider is "algolia": appId, apiKey (the search-only public API key, not an admin key) and indexName, exactly as Algolia's own DocSearch client expects them. insights (false by default) turns on DocSearch's click/conversion analytics.
  {
    "search": true,
    "searchProvider": {
      "provider": "algolia",
      "algolia": {
        "appId": "ABC123",
        "apiKey": "a1b2c3d4e5f6...",
        "indexName": "my-docs"
      }
    }
  }
  • pagefind - both keys optional when provider is "pagefind": bin (default "pagefind") is the CLI executable name/path, resolved against PATH when it's a bare name; options is an array of extra raw CLI flags passed straight through. The pagefind CLI itself must already be installed and on PATH - BX Sites shells out to it (like git for lastUpdated/gh-deploy), it doesn't install it for you.
  {
    "search": true,
    "searchProvider": {
      "provider": "pagefind",
      "pagefind": { "bin": "pagefind", "options": [] }
    }
  }

By default, nav is inferred from docs/'s own folder/file structure (with order/hidden frontmatter) - fine for small sites, but a large one can outgrow it: an explicit nav lets you title, group and order pages however you want, independent of where their files actually live.

An empty array (the default) means "infer from folder structure". A non-empty array replaces that inference entirely - array order becomes nav order, and a page not referenced anywhere in it is still built, just not linked from the nav (same as hidden: true). Each entry is either:

  • a bare docs/-relative path string, e.g. "guides/setup.md" - title comes from that page's own frontmatter/filename, same as folder-inference would give it
  • an object { "title", "path", "icon", "children" } - path, icon and children are all optional; a title-only entry with no path is an unlinked group heading (like a folder with no index.md today), and an explicit title/icon always overrides the linked page's own title/icon in the nav (the page's real <h1>/<title> is untouched - only the nav label/icon changes) - see Themes: Icons for what an icon value can be

A title-only entry with children and no path is exactly a menu container/section label - a non-clickable heading that just groups its children, the same role GitBook's "MAIN COMPONENTS" plays in its own sidebar:

{
	"nav": [
		"index.md",
		{
			"title": "Main Components",
			"children": [
				{ "title": "Quick Start", "path": "guides/setup.md" },
				"guides/deployment.md"
			]
		}
	]
}

Give that same group entry a path instead and it becomes a normal linked section (its own landing page, plus children) rather than a bare label - both shapes nest under theme.options.navCollapsible the same way (see above).

For a nav large enough that it clutters bxsites.json, move it to its own docs/nav.json file instead - same array shape, just as the whole file's top-level content:

[
	"index.md",
	{ "title": "Guides", "children": [ "guides/setup.md" ] }
]

bxsites.json's own nav, when non-empty, always wins over docs/nav.json. Only the main tree honors either - a docs/versions/<name>/ tree always infers its nav from its own folder structure, even when the main tree has an explicit one.

markdown

Forwarded as-is to bx-markdown's own module settings before each page renders. BX Sites doesn't redefine or validate these keys; whatever you put here is bx-markdown's own option set, straight through - so this list can drift from bx-markdown's own as it evolves. Tables, ~~strikethrough~~, - [ ] task-list checkboxes and the in-page table of contents are always on, with no toggle. The one exception is enableAdmonition - bx-markdown itself defaults it to false, but BX Docs defaults it to true (see the Markdown Extensions guide).

KeyDefaultEffect
enableAdmonitiontrue (BX Sites default; bx-markdown's own default is false)!!!/???/???+ callout blocks - see the Markdown Extensions guide
enableFootnotesfalse[^label] footnote references - see the Markdown Extensions guide
enableDefinitionListsfalseTerm\n: Definition lists - see the Markdown Extensions guide
autoLinkUrlstrueAuto-links bare URLs and email addresses
anchorLinkstrueAdds a clickable anchor link to every heading
anchorSetIdtrueStamps an id attribute onto every heading
achorSetName (sic)trueStamps a name attribute onto every heading
anchorWrapTextfalseWraps the whole heading text in the anchor link, instead of just a bare marker
anchorClass"anchor"CSS class on the anchor <a>
anchorPrefix / anchorSuffix""Raw HTML injected immediately before/after the heading text
enableYouTubeTransformerfalseAuto-embeds bare YouTube links as a player
codeStyleHTMLOpen / codeStyleHTMLClose"<code>" / "</code>"Wrapper HTML around inline code spans
fencedCodeLanguageClassPrefix"language-"Class prefix bx-sites's client-side syntax highlighter (and Mermaid, see below) key off of, e.g. ```js -> class="language-js"
tableOptions.columnSpanstrueHonors colspan-style merged table cells
tableOptions.appendMissingColumnstruePads a short row out to the header's column count
tableOptions.discardExtraColumnstrueDrops extra cells in an over-long row
tableOptions.className"table"CSS class on every rendered <table>
tableOptions.headerSeparationColumnMatchtrueRequires the --- separator row to match the header's column count
{
	"markdown": {
		"enableFootnotes": true,
		"enableDefinitionLists": true,
		"anchorLinks": false,
		"enableYouTubeTransformer": true
	}
}

repo

Adds a repository icon link to the header (all three built-in themes) and, when both keys are set, an "Edit this page" link on every page.

  • repo.url - your repo's URL, e.g. "https://github.com/acme/docs". Renders the header icon link on its own; leave blank to omit it entirely.
  • repo.editUri - the path segment between the repo URL and a page's own source path, e.g. "edit/main/docs/" (GitHub's own "edit" URL convention). Combined with repo.url and a page's docs/-relative source path to build its edit link - e.g. with the example above, docs/guides/setup.md gets https://github.com/acme/docs/edit/main/docs/guides/setup.md. Requires repo.url too; leave blank to omit edit links while still showing the header icon.
{ "repo": { "url": "https://github.com/acme/docs", "editUri": "edit/main/docs/" } }

social

An array of social/external links rendered in the footer (see footer - has no effect unless it's also turned on). Each entry needs a url; icon selects from a small built-in icon set (github, twitter/x, youtube, linkedin, facebook, bluesky, threads, slack, patreon, rss, email, falling back to a generic link glyph for anything else), and label sets the link's accessible name/tooltip (defaults to icon, then "Link").

{
	"social": [
		{ "url": "https://twitter.com/acme", "icon": "twitter", "label": "Twitter" },
		{ "url": "https://acme.com/rss.xml", "icon": "rss", "label": "RSS" }
	]
}

false (the default) - no footer at all. true adds one to every page: a copyright line (© <year> <site name>), the social links (if any), and a "Built with BX Sites" credit.

{ "footer": true }

lastUpdated

false (the default) - no last-updated date. true adds a "Last updated" line next to the edit link (or on its own, if repo.editUri isn't set), sourced from git log on each page's own Markdown file at build time. Silently omitted for a page git has no history for - a fresh git init with no commits yet, a build running from a downloaded zip with no .git at all, or git not being installed on the build machine - rather than breaking the build.

{ "lastUpdated": true }

analytics

Wires up pageview analytics. Currently supports Google Analytics (gtag.js) only:

  • analytics.provider - "google" to enable it; left blank (the default), no analytics script is shipped at all.
  • analytics.id - the Google Analytics measurement ID (e.g. "G-ABC123"). Required when provider is "google".
{ "analytics": { "provider": "google", "id": "G-ABC123" } }

ogImage

Path/URL to a default social-card image, rendered as og:image (and paired with a twitter:card of summary_large_image) on every page that doesn't override it - resolved the same way as theme.logo (relative paths are prefixed with baseURL, absolute URLs are used as-is). Left blank (the default) and generateOgImages off, no og:image/twitter:card tags are rendered.

{ "ogImage": "assets/social-card.png" }

A page's own frontmatter ogImage (see Getting Started) always wins over this site-wide default for that one page.

generateOgImages

false (the default) - no per-page cards. true renders a real 1200x630 PNG social card for every page that doesn't already have its own frontmatter ogImage - the page's title on the brand gradient, written to site/assets/og/<page>.png - instead of every page sharing one generic site-wide image. Pure java.awt/javax.imageio under the hood (part of any JVM BoxLang runs on), so this needs no headless browser, external service, or network access at build time.

{ "generateOgImages": true }

extraCss / extraJs

Arrays of extra stylesheet/script URLs to include on every page, appended after the theme's own assets - each entry is resolved the same way as theme.logo (a relative path is prefixed with baseURL; an absolute URL is used as-is). extraJs entries are loaded with defer.

{
	"extraCss": [ "assets/custom.css" ],
	"extraJs": [ "assets/custom.js" ]
}

When assets.bundle is on (the default), a local extraCss/extraJs list like the one above is bundled into one fingerprinted file each, instead of one <link>/<script> tag per entry - see assets below.

assets

{
	"assets": {
		"fingerprint": true,
		"bundle": true,
		"images": {
			"enabled": true,
			"widths": [ 400, 800, 1200, 1600 ],
			"formats": [ "original", "webp" ]
		}
	}
}

The asset pipeline - image resizing/WebP via bx-image (a required dependency, installed alongside bx-markdown/bx-esapi) and CSS/JS bundling. Everything here is on by default with reasonable settings - a fresh bxSites new project needs to touch none of this. See Responsive Images for the full picture, including what deliberately isn't covered (AVIF, animated GIFs, SVGs).

  • assets.fingerprint - true (the default). Content-hash-names every generated image variant and CSS/JS bundle (e.g. screenshot-800w.a3f9c2e1.webp, bundle.a3f9c2e1.css) so they can be served with safe, far-future cache headers - a project's build changes the file's own name only when its content actually changes. Does not rename a project's own original files under docs/assets/ - only pipeline-generated output gets fingerprinted, so anything else that references an asset by its plain filename (a ::: file download card, a raw markdown link) keeps working unchanged.
  • assets.bundle - true (the default). Concatenates extraCss/extraJs into one fingerprinted file each - pure BoxLang/JVM, no Node/esbuild toolchain. Falls back to today's exact per-URL <link>/<script> behavior, untouched, the moment any entry in the list is an external URL (a CDN link) or names a file that doesn't exist - see Responsive Images.
  • assets.images.enabled - true (the default). Every eligible docs/assets/** image (.png/.jpg/.jpeg) gets resized/WebP variants generated via bx-image, and every matching <img> gets rewritten into a <picture> with srcset. Set false to fall back to plain, unprocessed image copying, exactly as before this feature existed.
  • assets.images.widths - breakpoints to generate, in pixels. A width at or above a given image's own width is skipped automatically for that image - nothing is ever upscaled.
  • assets.images.formats - "original" keeps the source format as the <img> fallback; "webp" adds a same-size <source type="image/webp"> variant. Both on by default.

mermaid

false (the default) - no Mermaid diagram support shipped at all. true loads mermaid.js client-side and renders every ```mermaid fenced code block as a diagram. See Markdown Extensions for the syntax.

{ "mermaid": true }

math

false (the default) - no KaTeX shipped at all. true loads it client-side and typesets $...$/$$...$$ written directly into a page's markdown. See Markdown Extensions for the syntax.

{ "math": true }

Admonitions (note/warning/tip-style callout boxes), content tabs, and fenced-code hl_lines/linenums/title annotations are always available in every page's markdown, no config needed - see Markdown Extensions.

plugins

[] (the default) - an array of BoxLang module names to activate as plugins. Installing a plugin module (box install) never activates it on its own; it has to be named here too. See Plugins for how to write one.

{ "plugins": [ "myBxSitesPlugin" ] }

i18n

Metadata for the docs/i18n/<code>/ locale-folder convention - a locale builds automatically once its folder exists; i18n just supplies its display label/direction for the language switcher.

  • i18n.defaultLocale - { "code", "label", "flag" } for the project's own regular docs/ tree, defaulting to { "code": "en", "label": "English" }. Only needs setting when your default locale isn't English.
  • i18n.locales - [] (the default) - an array of { "code", "label", "dir", "flag" } for every other locale. code doubles as the docs/i18n/<code>/ folder name and the built URL prefix - letters/digits/hyphens only (es, pt-BR, zh-Hans). dir is "ltr" (the default) or "rtl". flag is an optional emoji override for the language switcher's flag icon - most common codes already resolve to a sensible flag on their own.
{
	"i18n": {
		"defaultLocale": { "code": "en", "label": "English" },
		"locales": [
			{ "code": "es", "label": "Español" },
			{ "code": "ar", "label": "العربية", "dir": "rtl" }
		]
	}
}

See Internationalization for the full picture - untranslated-page fallback, the language switcher, and what isn't translated yet.

blog

Options for the blog - itself a by-convention feature (docs/blog/posts/), no key here required to turn it on.

  • blog.postsPerPage - 10 (the default) - how many posts per page on /blog/, every category page, and every /blog/archive/<year>/ page before it moves to .../page/2/.
  • blog.feed - true (the default) - whether /blog/feed.xml (RSS 2.0) is written. Only meaningful with an absolute baseURL, same requirement as sitemap.xml.
  • blog.feedLimit - 25 (the default) - caps /blog/feed.xml to this many most-recent posts. 0 means unlimited (every post, in full). Most feed readers only care about what's new, so an unbounded feed on a blog with hundreds of posts just wastes bandwidth on every poll - see Blog: Feed.
{ "blog": { "postsPerPage": 10, "feed": true, "feedLimit": 25 } }

See Blog for post/author frontmatter, categories, featured images, and SEO/social metadata.

Versioning

Versioned docs are convention over configuration - there's no bxsites.json key for it. Add a docs/versions/<name>/ folder and it builds automatically as its own doc tree, with a version switcher every theme renders for free once more than one version exists. See Versioning for the full picture - cutting a new version with version:new, how versions sort and build, and what's out of scope (per-tree search scoping, no deprecated/EOL flag).

Edit this page Download Markdown Last updated Aug 23, 2026, 2:17:28 AM