Skip to content

Configuration

Everything you can change lives in two files: luaudocs.toml for what the site is, and .luaudocs/custom.css for how it looks.

Both are optional: without them, dev and build run on the defaults that gave you your first site. This page is for when you want something other than the default.

Scaffolding it

bash
luaudocs init

init writes a luaudocs.toml (filling in [repo] url from your git remote) and the pages you own: a landing page, a starter guide, and custom.css. It also writes the GitHub Pages workflow to .github/workflows/docs.yml, unless you already have one.

Which files are yours

Everything you author sits at the top of .luaudocs/. Show hidden files if you cannot see the directory.

PathOwner
guide/, public/, custom.cssYours. Written once by init, never touched again.
index.mdYours, once it stops carrying the # Generated by luaudocs marker (details below).
api/, .vitepress/The tool's. Anything you put inside is deleted by the next build.
package.json, changelog.md, public/llms*.txtRegenerated in place every build.
.gitignoreCreated once when missing.

There is no VitePress config to edit

If luaudocs.toml and custom.css cannot express what your docs need, please open an issue rather than attempting workarounds inside api/ or .vitepress/, which future builds will overwrite.

Claiming the landing page without running init

While you have no index.md, every build supplies one from your README.md (or a plain hero), marked # Generated by luaudocs at the top of its frontmatter, and rewrites it on every build. init replaces a still-marked file with its own hero, which does not include the README. Removing that marker line ensures neither build nor init will overwrite your changes.

luaudocs.toml

Here is the full set of configuration keys. All are optional, and commented-out entries represent advanced options that most sites never need to change:

toml
title = "My Library"                  # defaults to the project folder name
description = "One-line tagline"      # site description / meta tag

[source]
entries = ["src"]                     # module roots (dir with init.luau, or a file; [] = guide-only)
# include = ["**/*.luau"]             # only document modules matching these globs
# exclude = ["**/*.spec.luau"]        # drop matching modules from the docs
# projectFile = "my.project.json"     # Rojo project mapping the instance tree (default: default.project.json)

[repo]                                # "view source" + edit links + GitHub icon
url = "https://github.com/you/repo"   # (init fills this in from your git remote)
branch = "main"

# [site]                              # branding (paths live in .luaudocs/public/)
# siteTitle = "Short Name"            # navbar text (defaults to title), or false for logo-only
# logo = "/logo.svg"                  # header logo, or { light = "…", dark = "…" }
# favicon = "/favicon.ico"
# ogImage = "/og.png"                 # social-preview image (og:image / twitter:card)
# footer = { message = "MIT", copyright = "© you" }  # shown on pages without a sidebar

# [[site.nav]]                        # header navbar (unset: Guide / API / Changelog as applicable)
# text = "Guide"
# link = "/guide/getting-started"
# activeMatch = "^/guide/"            # routes that mark the entry current (default: its link alone)
# [[site.nav]]                        # nest [[site.nav.items]] under an entry for a dropdown
# text = "API"
# link = "/api/"

# [[site.head]]                       # extra head tags on every page (analytics, verification)
# tag = "script"
# attrs = { defer = "true", src = "https://plausible.io/js/script.js", "data-domain" = "docs.example.com" }
# content = "..."                     # inline body, for tags that carry one

[api]
includePrivate = false                # render _members and @private (badged)
linkRobloxTypes = true                # link Roblox classes/enums/datatypes to creator docs

[docs]
# dir = ".luaudocs"                   # site root (your content + the generated machinery)
url = "https://docs.example.com"      # published URL: sitemap.xml + derived base path
# changelog = false                   # opt out of the CHANGELOG.md -> /changelog page
# includeReadme = true                # keep a README include fresh for your landing page (see below)
# llms = false                        # opt out of llms.txt + llms-full.txt

Unknown keys are errors, so a typo cannot quietly disable a setting.

A few notes on keys whose behavior is not obvious from the comment:

  • Base path. A path in [docs] url (user.github.io/repo/) becomes the site's base; see Deploying.
  • AI tooling. llms.txt indexes the site and llms-full.txt carries every page's markdown in one stream. Only build writes them, so a dev session serves whatever the last build produced.
  • The API sidebar. It comes from your library's public access paths and takes no configuration at all; see How It Works.
Putting your README on a landing page you author

While you have no index.md, the build already makes your landing page out of README.md, so a README-fronted site needs no key at all.

includeReadme = true is for the other case. Each build regenerates .vitepress/generated/readme.md from your README.md (honoring moonwave-hide-before/after-this-line markers), and you pull it into your own index.md with a VitePress file inclusion: an HTML comment wrapping @include: ./.vitepress/generated/readme.md.

Either way, the README's relative links are respelled for the site: a target inside your docs dir becomes that page (or public/ asset), and any other repository file (LICENSE.txt, CONTRIBUTING.md, images) links to the repository through [repo], so set it before fronting a README that carries such links.

The navbar

Unset, the navbar derives itself: a Guide entry pointing at your first guide page, an API entry when API pages were generated, and a Changelog entry when that page is generated. The first two link one page of a section, so they also carry an activeMatch (^/guide/, ^/api/) that keeps the entry marked current across the whole section rather than only on the page it links.

[[site.nav]] replaces that derived set wholesale, so if you define your own entries, add a /changelog one when you want it, and give any entry heading a section its own activeMatch.

The guides sidebar

The /guide/ sidebar comes from each page's frontmatter, and subdirectories nest as groups labeled from the folder name:

yaml
---
title: Getting Started # or sidebar_label; falls back to the first heading
sidebar_position: 1 # or order
description: One-line summary # optional; else the first sentence of the page
---

A position is read against the page's siblings alone, so every folder numbers from 1: the pages in guide/reference/ order among themselves, not against the pages sitting beside that folder. A page declaring no position sorts after the ones that do, by title. An index.md leads the folder it sits in whatever the positions around it say, guide/index.md included.

description never reaches the sidebar. It is the page's summary in llms.txt, taken verbatim.

A folder or page whose name starts with an underscore (_drafts/, _partial.md) is left out entirely: not in the sidebar, not built as a page, not in search or llms.txt. That is where an unfinished page or an <!--@include: --> partial lives, as in Docusaurus.

The pages sit under one Guide heading. When guide/ contains only subfolders, the top-level Guide heading is omitted and each folder becomes its own distinct section. A section header has no toggle unless its group settings specify collapsed or collapsible.

Group settings

A folder is configured by a _category_.json (or .yml, or .yaml) sitting beside its pages, the same sidecar Docusaurus and Moonwave read:

json
{
	"label": "API Reference",
	"position": 3,
	"collapsed": true
}
  • label names the group. Without it, the folder name is converted to title case (e.g., reference/ becomes "Reference").
  • position places the group among its siblings. Any number Docusaurus accepts works, 2.5 included.
  • collapsed starts the group closed. Groups start open.
  • collapsible set to false drops the toggle entirely: the group is always open, and a collapsed beside it does nothing.

A folder with an index.md, the page that serves at the folder's own address (/guide/reference/), can put sidebar_position, collapsed, and collapsible in that page's frontmatter instead, eliminating the need for a separate _category_.json when the folder already has a landing page:

yaml
---
title: Overview
sidebar_position: 3
collapsed: true
---

When both set a key, the sidecar wins. There is no frontmatter label: an index page's title names the page, not the group.

A _category_.json that does not parse fails the build.

Styling

init scaffolds .luaudocs/custom.css, and every page loads it. Override brand colors, the hero gradient, or any default-theme variable there. Delete the file and the next build stops loading it.

It loads after the tool's own stylesheet, so anything you set there wins.

The code font

Code blocks and inline spans are set in Fira Code, which ships with LuauDocs and is served from your site rather than a font CDN. Its programming ligatures are on, so ->, .., and <= render as single glyphs. Luau's ~= is not among them (Fira Code ligates !=), so inequality reads exactly as written.

Either one is a line in custom.css:

css
/* keep the font, drop the ligatures */
.vp-doc code {
	font-variant-ligatures: none;
}

/* or use something else entirely */
:root {
	--vp-font-family-mono: "JetBrains Mono", monospace;
}

Guide-only sites

Set entries = [] to skip the extractor entirely: no /api/ pages, just your landing page, guides, and changelog.

This site is nearly one. It has no Luau of its own, so entries points at examples/Flux, a demo library whose generated pages these guides embed inline.