Skip to content
Article Writing documentation
☀️ Light Clean and bright
📜 Sepia Warm and vintage
🌤️ Light Gray Subtle and neutral
🔵 Light Blue Calm and serene
🌙 Dark Easy on the eyes
🌑 Dark Gray Deep and modern
🔷 Dark Blue Professional and sleek
🌿 Forest Calm and natural

Writing documentation

Documentation is Markdown on disk. A page is a file, the file path is the URL, and there is no publish step — save it and it is live.

This page is the reference for that format. It is itself a normal page, so if you want to see how something is done, open content/pages/writing-docs.md.

Anatomy of a page

---
title: Access tokens
description: Issuing, refreshing and revoking API tokens
order: 40
---

# Access tokens

Opening paragraph — one or two sentences on what this page covers and who
it is for.

## First section

Body text.

Three parts, in this order, always:

  1. Front matter — a --- fenced block, first line of the file.
  2. One H1 — the visible page heading.
  3. Body — everything else.

Where files live

All pages live under content/pages/. Directories become URL segments.

content/pages/
├── index.md            → /docs
├── getting-started.md  → /docs/getting-started
├── writing-docs.md     → /docs/writing-docs
└── api/
    ├── index.md        → /docs/api
    └── tokens.md       → /docs/api/tokens

Naming rules. Lower case, words separated by hyphens, no spaces, no underscores, no dates in the filename. The name is a permanent URL — choose it for the topic, not for today's version of the topic.

Renaming breaks links. There is no redirect table. If you rename a page, grep the tree for the old slug and fix every reference in the same commit.

Images do not live here. The page store serves only the extensions on its allow-list, so it will refuse to hand out a .png even if you put one in content/pages/. Images belong under public/.

Front matter

The block is a small YAML subset. Keys are lower case with underscores. Values are plain — no anchors, no nested maps, no multi-line scalars.

KeyTypeEffect
titlestringPage title, sidebar label, browser tab. Required.
descriptionstringMeta description, and the card text where this page is linked from
ordernumberSidebar sort position. Lower comes first. Default 500
herobooleantrue turns the page into a landing page. The root index gets one by default
subtitlestringThe large centred line under a hero heading. Hero pages only
eyebrowstringSmall accent label above a hero heading. Hero pages only
quick_linkslistCards below the hero
sponsorslistLogo or name strip at the foot of the page
sponsors_titlestringHeading above that strip. Default Supported by

Only title is required. On an ordinary page you will rarely need more than three keys:

---
title: My page
description: One line, used in link cards and the meta tag
order: 3
---

Choosing an order

Number in tens, not in ones — 10, 20, 30. When you insert a page between two others you want a gap to put it in, not a renumbering of the whole section.

Each entry is one of two things:

A slug. The linked page's own title and description fill the card:

quick_links: [api/tokens.md, getting-started.md]

A pipe tripleLabel|url|note — for anything outside the docs tree, or when you want different card text:

quick_links: [Changelog|/docs/changelog|What shipped recently]

The two forms mix freely in one list.

Commas separate entries. A comma inside a label or a note splits the entry in two and produces a broken half-card. First run, config and your first page becomes two list items. Rewrite the note without the comma — First run and initial config — or add quoting support to the parser before you rely on it.

Leave the key out entirely and a landing page lists the first six other pages.

sponsors

Same shape, as Name|url|logo. The logo is optional; without one the name renders as a text chip:

sponsors: [Acme|https://acme.test|/public/img/acme.svg, Initech|https://initech.test]

Logo paths follow the same rule as images — under public/, starting with /.

Headings

  • One H1 per page, matching title. It is the page heading, not a section.
  • Sections are H2. Subsections are H3.
  • Never skip a level. H2 → H4 breaks the generated table of contents.
  • H4 and deeper do not appear in the contents panel. If you need one, that is usually a sign the page should be split.
  • Sentence case: Access tokens, not Access Tokens.
  • No trailing colons, no numbering. The renderer derives anchors from heading text, so ## Refresh tokens gives #refresh-tokens. Changing a heading changes its anchor and breaks inbound deep links.

Text

Wrap at 80 columns. Long lines make diffs unreadable, and a diff you cannot read is a doc that does not get reviewed.

Standard emphasis applies:

**bold** for the first use of a term or a genuine warning
*italic* sparingly
`code` for anything typed literally: files, keys, methods, values

Use code for every identifier — RouterSettings, .env, AUTH_DB_DRIVER, /api/v2/data. Prose that names a class without marking it as code is prose the reader has to parse twice.

Lists

Hyphens for unordered, 1. for ordered. Indent nested items by two spaces.

Ordered lists are for genuine sequences — steps that must happen in that order. If the order does not matter, use bullets. Numbering unordered material implies a dependency that is not there.

Code blocks

Always fence, always tag the language:

```php
$settings = RouterSettings::apiBase();

Common tags in this project: `php`, `js`, `html`, `css`, `json`, `sql`, `ini`,
`bash`, `markdown`.

Rules that keep examples useful:

- **Runnable, not illustrative.** Paste-and-run beats pseudo-code.
- **No real credentials.** Not even expired ones, not even in a comment.
- **Trim the noise.** Show the lines that matter, not the whole file.
- **Prefix shell commands with nothing.** No `$`, so the reader can copy the
  line without editing it.

## Tables

Pipe tables. Keep them narrow — a table that wraps is worse than the list it
replaced.

```markdown
| Key      | Type   | Default |
| -------- | ------ | ------- |
| `title`  | string | —       |
| `order`  | number | `500`   |

Use an em dash for "no value", not an empty cell. Empty cells read as an oversight.

Callouts

A blockquote is a callout. There is one level of emphasis, so use it rarely — three callouts on a page means none of them is read.

> Images belong under `public/`, not under `content/pages/`. The page store only
> serves the extensions on its allow-list, so it will not hand out a `.png`.

Reserve callouts for things that cost the reader real time: a footgun, a destructive operation, a behaviour that contradicts a reasonable assumption. Anything that is merely useful is a normal paragraph.

Internal links use the routed path, not the file path:

[Access tokens](/docs/api/tokens)

Paths beginning with / pick up the application's base path automatically, so the same link resolves whether the app sits at a domain root, in a sub-folder, or under WordPress. Never write a link with a host in it to another docs page, and never build one by hand from what you see in your own address bar.

External links carry their full URL. Prefer primary sources, and write the link text so it makes sense read aloud on its own — never "click here", never a bare URL in the middle of a sentence.

Images

Standard Markdown syntax. Paths start with / and point into public/:

![Request lifecycle](/public/img/lifecycle.png)

The renderer treats images specially depending on how they sit in the source:

An image alone in a paragraph becomes a captioned figure, using the alt text as the caption:

![The window manager after opening three apps](/public/img/windows.png)

Several images in one paragraph become a gallery grid:

![Light theme](/public/img/light.png) ![Dark theme](/public/img/dark.png)

Alt text does double duty here — it is the accessibility text and the visible caption. Write it as a sentence describing what the reader should notice, not as a filename restated.

House style

Address the reader as "you". Never "we", never "the user".

Present tense, active voice. "The router resolves the slug", not "the slug will be resolved by the router".

Say what happens, then why. The reader is looking for the behaviour; the rationale is what they read second.

Document the current version. No "as of 1.4", no "this used to". History belongs in the changelog.

No unexplained abbreviations. Expand on first use, once per page.

Do not write about the documentation. "This section will explain…" is a sentence that could have been the explanation.

Editing pages

Signed in as an admin, the toolbar in the top bar edits the current file in place, creates a new page, or deletes one.

The same operations are available over JSON at /api/v2/pages. See the API pages for request shapes.

Whichever route you use, the result is a file in the repository. Treat it that way: commit it, review it, and change it in the same pull request as the code it describes.

Before you publish

  • [ ] Front matter has title; description and order set unless there is a reason not to
  • [ ] Exactly one H1, matching title
  • [ ] No skipped heading levels
  • [ ] Every code fence tagged with a language
  • [ ] No credentials, keys, tokens or real host names in examples
  • [ ] Internal links use /docs/... paths and all of them resolve
  • [ ] Images under public/, every one with alt text
  • [ ] No commas inside quick_links or sponsors entries
  • [ ] Lines wrapped at 80 columns
  • [ ] Page renders — open it before you push