Built-in platform schemas
Eight built-ins describe the front matter contract of a documentation toolchain that is not Docusaurus. Like the Docusaurus schemas, they are platform schemas: they say what your site generator accepts, not what your team has agreed a page should say. That makes them safe to stack under any vocabulary.
| Id | Toolchain | Files it fits | Requires |
|---|---|---|---|
astro:starlight:0.41 |
Astro Starlight 0.41 | .md, .mdx |
title |
antora:page:3.1 |
Antora 3.1 | .adoc, .asciidoc |
title |
sphinx:docinfo:9.1 |
Sphinx 9.1 | .rst |
nothing |
myst:frontmatter:1.10 |
mystmd 1.10 | .md |
nothing |
hugo:page:0.165 |
Hugo 0.165 | .md |
nothing |
jekyll:page:4.4 |
Jekyll 4.4 | .md, .markdown |
nothing |
vitepress:page:1.6 |
VitePress 1.6 | .md |
nothing |
mkdocs:material:9.7 |
MkDocs 1.6 + Material 9.7 | .md |
nothing |
What a platform schema requires
Section titled “What a platform schema requires”The rule across every platform built-in is to require exactly what the generator refuses to build without. What one demands is therefore a fact about the tool, not a judgement docmeta made on your behalf.
Docusaurus and Sphinx mark no field as mandatory, so their schemas require
nothing. They can only ever fail a page on the shape of a value it already
carries. Starlight and Antora both error without a page title, so both schemas
demand title. A page failing that check was already broken, and docmeta is
telling you at review time instead of at build time. MyST requires nothing
because it will lift a missing title from the first heading in the document.
Hugo, Jekyll, VitePress and MkDocs require nothing either, for the plainest
reason of the lot. All four render a page that has no front matter at all. Hugo
falls back to a title derived from the file name. MkDocs works down a list of
four title sources and stops at the first that answers. VitePress has a default
for every option. In Jekyll it is the presence of the --- block, not its
contents, that marks a file for processing. An empty one is valid. That makes
all four pure shape checks, which is where their value is. The mistakes they
catch are the ones the generator accepts without complaint and then acts on
wrongly.
astro:starlight:0.41
Section titled “astro:starlight:0.41”Starlight defines its front matter as a schema you extend rather than replace, so unknown keys are expected and tolerated here too.
| Field | Type | Notes |
|---|---|---|
title |
string | Required. Non-empty. |
description |
string | Used for page metadata and SEO. |
slug |
string | Overrides the URL slug taken from the file path. |
editUrl |
string | boolean | A URL, or false to hide the edit link. |
head |
array | Tags injected into <head>; each needs a tag. |
tableOfContents |
object | false |
minHeadingLevel and maxHeadingLevel, each 1–6. |
template |
doc | splash |
Anything else is rejected. |
hero |
object | title, tagline, image, actions. |
banner |
object | Requires content. |
lastUpdated |
date | boolean | A YAML date, or a flag. |
prev, next |
boolean | string | object | false hides, a string relabels, an object sets link and label. |
pagefind |
boolean | Search indexing. Defaults to true. |
draft |
boolean | Excluded from production builds. |
sidebar |
object | label, order, hidden, badge, attrs. |
tableOfContents is written with if/then rather than anyOf so a bad
heading level is reported against /tableOfContents/maxHeadingLevel. An anyOf
collapses every branch failure onto the parent object, which puts the caret on
the wrong line.
antora:page:3.1
Section titled “antora:page:3.1”Antora page metadata lives in the AsciiDoc document header, not in front matter.
The reader takes the = Title line plus every :name: value attribute entry
above the first blank line.
| Attribute | Type | Notes |
|---|---|---|
title |
string | Required. The = Title line. |
description |
string | AsciiDoc built-in; becomes an HTML meta tag. |
keywords |
string | AsciiDoc built-in. A comma-separated string, never a list. |
navtitle |
string | Link text used by the navigation. |
page-aliases |
string | Comma-separated alternate resource IDs. |
page-layout |
string | Which UI template renders the page. |
page-partial |
boolean | :page-partial: is true, :!page-partial: is false. |
page-role |
string | Body classes. The default UI reserves -toc. |
page-toclevels |
integer | 0–5. |
page-tags |
string | Comma-separated, same typing as keywords. |
Two things about AsciiDoc typing catch people out, and the schema is built around both:
- Attribute values are text.
:keywords: setup, referencereaches the validator as the single string"setup, reference". Typing it as an array would fail every real Antora page, so it is a string. - A bare attribute is
true.:page-partial:with no value is the booleantrue, and the unset form:!page-partial:isfalse. Numbers are typed too, so:page-toclevels: 3is the number 3 and not"3".
Antora defines only three predefined page attributes: page-aliases,
page-layout and page-partial. The rest of the page-* namespace is yours,
and custom attributes pass unchecked.
sphinx:docinfo:9.1
Section titled “sphinx:docinfo:9.1”Sphinx reads file-wide metadata from a field list at the top of the document. Every field is optional, and flag fields are written bare.
| Field | Type | Notes |
|---|---|---|
tocdepth |
integer | Local toctree depth. Does not affect the global sidebar. |
orphan |
boolean | Suppresses the not-in-any-toctree warning. |
nocomments |
boolean | Suppresses the comment form. |
no-search |
boolean | Excludes the page from full-text search. |
nosearch |
boolean | Deprecated spelling of no-search. |
The set is small on purpose. Sphinx parses every docinfo value as a string, so a field list cannot carry a list or a mapping. Anything richer belongs somewhere else.
myst:frontmatter:1.10
Section titled “myst:frontmatter:1.10”The largest built-in, and the one whose value is mostly in its length caps. MyST silently truncates or rejects over-long values, so the caps are the constraint a real document trips.
| Field | Cap |
|---|---|
title, subtitle, description, label |
500 characters |
short_title, subject |
40 characters |
Beyond those, the schema types the identity and publication fields. Those are
authors, editors, reviewers, affiliations, doi, arxiv, pmid,
pmcid, license, open_access, venue, volume, issue and funding. It
also types the source links github, edit_url, source_url and binder, and
the execution blocks kernelspec and execute. pmcid is checked against
PMC followed by digits.
Project-level keys that belong in myst.yml rather than a page are not
described, and pass as unknown keys. Those are bibliography, references,
requirements and social.
hugo:page:0.165
Section titled “hugo:page:0.165”The 28 fields Hugo itself defines. Everything else you see in a Hugo site is a
user-configured taxonomy or a custom parameter rather than something Hugo
defines. It passes as an unknown key. That covers tags, categories,
series, and whatever your theme reads.
| Field | Type | Notes |
|---|---|---|
title, linkTitle, description, summary |
string | None required; Hugo derives a title from the file name. |
date, lastmod, publishDate, expiryDate |
string | Typed, not format-checked. See the aside below. |
draft, headless, isCJKLanguage |
boolean | A quoted "false" fails here, which is the point. |
weight |
integer | Lower sorts first. A quoted number sorts as a string. |
aliases, keywords, outputs |
string array | |
slug, url, layout, markup, type, translationKey |
string | |
menus |
string | array | map | One menu, several, or a map of per-menu options. |
cascade |
map | array of maps | The array form carries a _target filter per entry. |
build |
map | list, render, publishResources. |
sitemap |
map | changefreq is enumerated; priority is 0–1. |
resources |
array | Each entry needs src. |
params, sites |
map |
The two checks that pay for this schema are weight and draft. Both are
routinely written quoted, both are accepted by Hugo without a word, and both
then behave wrongly. A string weight sorts lexically, so "10" lands before
"9". And draft: "false" is a non-empty string, which is truthy, so the page
does not publish.
jekyll:page:4.4
Section titled “jekyll:page:4.4”Deliberately the smallest built-in, because Jekyll’s own contract is small: three global variables and four more for posts.
| Field | Type | Notes |
|---|---|---|
layout |
string | null | null or none suppresses the layout, so null is allowed. |
permalink |
string | Overrides the built URL. |
published |
boolean | false keeps the document out of the site. |
date |
string | Overrides the date parsed from the file name. |
category, categories, tags |
string | string array | A YAML list or a space-separated string. |
The check worth having is published. published: "false" is a string, every
string is truthy, and the post you meant to hold back goes live.
vitepress:page:1.6
Section titled “vitepress:page:1.6”The four options every VitePress theme honours, plus the default theme’s page-level toggles.
| Field | Type | Notes |
|---|---|---|
title, description |
string | Override the site-level values. |
titleTemplate |
string | boolean | false drops the suffix. |
head |
array | [tag, attrs] pairs injected into <head>. |
layout |
doc | home | page |
Anything else is rejected. |
hero, features |
object, array | Home-layout blocks; typed but not pinned. |
outline |
level | [from, to] | deep | false |
1–6. |
aside |
boolean | left |
|
lastUpdated |
boolean | string | A flag, or a date overriding the stamp. |
navbar, sidebar, editLink, footer, isHome |
boolean | Page-level toggles. |
pageClass |
string |
outline is the field this schema exists for. It takes four different shapes,
and VitePress ignores a value it does not recognise rather than complaining. So
outline: 9 silently gives you the default rather than a deep outline.
mkdocs:material:9.7
Section titled “mkdocs:material:9.7”The two keys MkDocs itself defines, plus the seventeen Material for MkDocs adds
on top of them. Material is the theme the overwhelming majority of MkDocs sites
run. It is where nearly all of the front matter surface lives. Core MkDocs reads
title and template and nothing else. So one id covers both, the way
vitepress:page:1.6 covers VitePress and its default theme in one.
| Field | Type | Notes |
|---|---|---|
title |
string | MkDocs. Fourth of four title sources, so never required. |
template |
string | MkDocs. The theme template rendering this page. |
description |
string | The <meta name="description">, and the social card’s text. |
author |
string | The <meta name="author">. One name, not a list. |
icon |
string | An icon shortcode, resolved as .icons/<value>.svg. |
status |
string | new and deprecated ship; others come from theme.extra.status. |
subtitle |
string | Rendered under the navigation entry. Material 9.6 and later. |
hide |
string array | navigation, toc, path, tags, footer, feedback. |
tags |
string array | Read by the tags plugin and added to the search index. |
search |
object | exclude and boost. |
social |
object | cards, cards_layout, cards_layout_options. |
date |
date | object | A post’s date, or a mapping requiring created. |
authors, categories |
string array | A post’s authors and categories. |
draft, pin |
boolean | Hold a post back; pin it to the top of a view. |
readtime |
integer | Overrides the computed reading time, in minutes. |
slug |
string | Overrides a post’s URL segment. |
links |
array | A post’s sidebar links, in nav shape. |
hide is the field this schema exists for. Material decides what to hide by
testing membership of the list, with "navigation" in page.meta.hide. It says
nothing at all about an entry it does not recognise, so hide: [sidebar] builds
cleanly and hides nothing. The six values are transcribed from the templates
that test them, not from a documentation page, so the list is what the theme
actually honours.
The rest of the value is in the typing. readtime, draft and pin go through
the blog plugin’s own configuration validation, which refuses a quoted number or
a quoted boolean outright. tags reaches a search plugin that checks
isinstance(tags, list) and skips a bare string without a word. boost is
copied into the search index exactly as written, so a quoted one ranks nothing.
Composing with a vocabulary
Section titled “Composing with a vocabulary”A platform schema claims the keys your generator reads. A vocabulary claims the keys your team agreed on. They mostly do not overlap, so one run can hold a page to both and attribute each failure to the schema that raised it:
docmeta validate docs/ -s astro:starlight:0.41 -s diataxis:diataxis:1.0title and description appear in several of these, and every definition
agrees that they are strings, so stacking is safe.
schemas: - astro:starlight:0.41 - diataxis:diataxis:1.0Antora content lives in per-component modules/*/pages trees, so the override
is usually a glob rather than a single root:
schemas: - google:okf:0.1overrides: - files: "**/modules/*/pages/**" schemas: - antora:page:3.1Sphinx and MyST can coexist in one tree, split by extension:
overrides: - files: "**/*.rst" schemas: - sphinx:docinfo:9.1 - files: "**/*.md" schemas: - myst:frontmatter:1.10Hugo’s content root is content/, and a docs section usually sits under it:
schemas: - google:okf:0.1overrides: - files: "content/**" schemas: - hugo:page:0.165 - diataxis:diataxis:1.0Both keep pages at the repo root, so the split is by directory rather than by extension:
overrides: - files: "_posts/**" schemas: - jekyll:page:4.4 - files: "docs/**" schemas: - vitepress:page:1.6MkDocs keeps everything under docs_dir, so one override covers the site:
schemas: - google:okf:0.1overrides: - files: "docs/**" schemas: - mkdocs:material:9.7 - diataxis:diataxis:1.0