Authoring
Folders, the four operations, releases and maintenance branches.
Folders are the versions
There is no versions.json. The list of versions is the list of top-level folders under the content
root, ordered by semver, with declared non-semver folders (next) sorted last.
content/docs/
1.0.0/ complete tree, frozen
2.0.0/ differences only
3.0.0/ differences only
next/ work in progress2, 3.1 and v1.2.3 are all accepted as version numbers. A folder that is neither a version number
nor a declared channel is ignored with an unknown-version-folder diagnostic — never silently.
Declare channels so an empty one still exists as a version:
overlaySource({ source: docs.toFumadocsSource(), channels: ["next"] });The four operations
Every one of them is expressed in the version that introduces it, so a published version folder is never edited again.
Add a page
Write the file in the version that introduces it. Nothing else.
Change a page
Write the file in the version that changes it. Older versions keep serving their own copy; newer ones inherit yours until someone changes it again.
Rename a page
renamedFrom on the new file:
---
title: New API
overlay:
renamedFrom: guide/old-api
---From this version onwards the old slug is a permanent redirect. In older versions it is still a real
page with a 200 — the rename is spatial, not retroactive. A list works too:
renamedFrom: [guide/old-api, guide/older-api].
Delete a page
A tombstone: a file at the slug you are removing, in the version that removes it.
---
title: Old API (removed)
overlay:
deleted: true
replacedBy: guide/new-api # optional
---The deletion version comes from the file's own path, so there is no version string to write and
nothing that can drift. grep -rl 'deleted: true' content/docs/3.0.0/ lists exactly what disappears in
that release, which makes it reviewable in a pull request.
recursive: true removes the whole subtree under the page's folder rather than just the page.
Aliases
A second slug for the same page, served with a canonical pointing at the real one:
---
title: API
overlay:
aliases: api-reference
---An alias never shadows a page, inherited ones included. To take a slug away from real content, use a real file or a tombstone.
Cutting a release
git mv content/docs/next content/docs/5.0.0 && mkdir content/docs/nextGit records that as renames, so the content diff is zero bytes, and the emptied channel inherits everything again. Compare with a snapshot-based tool, where the same operation copies the whole tree.
Maintenance branches
Semver order alone puts every version on one line, which is wrong the moment a hotfix exists:
11.13.1 should inherit from 11.13.0, and so should 11.14.0. Say so in code — there is still no
config file to maintain, and the version list is still just the folders:
overlaySource({
source: docs.toFumadocsSource(),
versions: {
"11.14.0": { inheritsFrom: "11.13.0" },
"11.13.1": { meta: { label: "11.13.1 (LTS)" } }
}
});Navigation
meta.json is inherited whole, per directory. A version that ships its own replaces the inherited one
completely.
One trap is handled for you. A meta.json whose pages list is exhaustive — no "..." — hides
anything it does not name. Inherited into a version that added a page, that page would be routed and
indexed by search but invisible in the sidebar. So when an inherited list cannot possibly have
known about a name, "..." is appended and a meta-pages-completed diagnostic says so. Names the
authoring version omitted on purpose stay omitted, because they were already omissions then.
Add "..." yourself if you want to control where new pages land.
Known limitation: i18n
Fumadocs' i18n.parser: "dir" consumes the first path segment — the same one the adapter uses for the
version. They cannot both own it, and 0.x does not support the combination.