Versions & i18n
Ship multiple documentation versions or languages from one project — same URL-prefix mechanism, a switcher in the header, fully static.
Markline serves multiple versions (v2, v1, …) or locales (en, es, …) from a single project. Both use the same idea: the first entry is the default and lives at the root; every other entry is served under a URL prefix with its own content folder — and the header shows a switcher.
How it works
There's one rule for the whole folder layout: the default version lives at the
content root; every other version lives in a folder named after its id. A
version folder is just a smaller mirror of the root — same docs/ and
api/openapi.json inside.
content/
├── markline.json # config + the versions list
├── docs/ # DEFAULT version docs → /quickstart
│ └── quickstart.mdx
├── api/
│ └── openapi.json # DEFAULT version spec → /api-reference
└── v1/ # one folder per non-default version id
├── docs/ # v1 docs → /v1/quickstart
│ └── quickstart.mdx
└── api/
└── openapi.json # v1 spec → /api-reference/v1
So a version id is both a folder name and a URL prefix. The one wrinkle to
remember — docs and the API reference prefix the id in different spots:
| Content file | Default URL | Version v1 URL |
|---|---|---|
docs/quickstart.mdx | /quickstart | /v1/quickstart |
api/openapi.json | /api-reference | /api-reference/v1 |
(Docs put the version first; the API reference keeps everything under
/api-reference and puts the version after it — so all API routes stay in one
place.)
api/ and it's
docs-only; drop docs/ and it's reference-only.Versions
List versions in markline.json. The first is the default; each entry is
{ id, label } and may carry its own navigation.
"versions": [
{ "id": "v2", "label": "v2 (latest)" }, // default — content in docs/, served at /
{ "id": "v1", "label": "v1" } // content in v1/docs/, served at /v1/…
]
Add the version content
Create v1/docs/ and author its pages — they're independent from the
default, so v1 can lag or differ freely.
(Optional) give it its own navigation
A version entry can set navigation: { tabs: [...] } to override the
sidebar for that version; otherwise it inherits the default navigation.
Ship
markline build emits every version statically. The header shows a
version switcher; picking one navigates under its prefix.
API reference per version
Each version can ship its own OpenAPI spec. Drop it at
<id>/api/openapi.json and Markline serves that version's reference at
/api-reference/<id> — generated exactly like the default, with its own
resource pages, code samples, and interactive explorer.
content/
├── api/openapi.json # default → /api-reference
└── v1/
└── api/openapi.json # v1 → /api-reference/v1
- The version selector in the API-reference header lists every version and
switches the spec in place (the default is marked
Default). - Within a version, every link — resource pages, search results, the explorer's
endpoint switcher — stays under that version's
/api-reference/<id>prefix, so you never fall back to the default spec mid-navigation. - Per-resource and per-operation MDX overlays are version-scoped too:
<id>/api/sections/<tag>.mdxand<id>/api/operations/<operationId>.mdx. - The pill label follows the spec's
info.version— a semver like1.4.2shows asv1 · 1.4.2; a date like2025-06-01shows once, verbatim.
api/openapi.json simply has no API reference (its
selector entry won't resolve a spec) — versions can be docs-only, reference-only,
or both.Localization
Locales work identically — i18n.locales, first is the default (unprefixed),
each other locale under its id prefix with content in <id>/docs/.
"i18n": {
"locales": [
{ "id": "en", "label": "English" }, // default — docs/, served at /
{ "id": "es", "label": "Español" } // es/docs/, served at /es/…
]
}
content/
├── docs/ # English (default) → /quickstart
└── es/
└── docs/ # Spanish → /es/quickstart
The header shows a language switcher that jumps to the same page under the chosen locale prefix.
Notes
- The default entry (first in the list) is never prefixed — keep your
canonical content in
docs/. - Each variant is fully static and independently navigable; search and the API reference work within each.
- Configure these in Configuration; for hosting the built output see Deployment.