Content
Theme documentation
Five collections, all defined and validated in src/content.config.ts. Three are folders of
Markdown, two are JSON files. There is no CMS and no database: the site is these files, built to
HTML.
Every page reads its collection through src/lib/content.ts rather than calling getCollection
itself, which is where the draft filter, the sort orders and the date format live. If you need a
new list of something, add the function there.
docs
src/content/docs/<slug>.md or .mdx. The filename is the URL: install.mdx is /docs/install.
| Field | Type | Required | Meaning |
|---|---|---|---|
title |
string | yes | The page heading, the sidebar row and the pager label |
description |
string | no | The lead under the heading, the meta description, and the line under the title on /docs |
group |
string | yes | Sidebar group. One of docs.groups in src/config.ts; anything else sorts to the end |
order |
integer > 0 | yes | Position inside the group. The previous/next pager follows it |
lastUpdated |
date | no | Printed at the foot of the page. Absent: no date |
draft |
boolean | no | true keeps the page out of the build, in dev as well |
---
title: Install
description: Install Quarry on macOS or Linux, then confirm the CLI and daemon are on your path.
group: Getting started
order: 2
lastUpdated: 2026-06-10
---Adding a page is adding a file: the sidebar, the group number, the breadcrumb, the pager and the search index all follow from the collection.
changelog
src/content/changelog/<file>.mdx, one file per release. The filename is not the URL — every
release is printed on the one /changelog page, newest first — so it needs no zero padding.
| Field | Type | Required | Meaning |
|---|---|---|---|
version |
string | yes | Sorted with numeric collation, so 1.10.0 lands above 1.4.0 |
date |
date | yes | Printed in the rail beside the version |
summary |
string | no | The release list and the feed. Plain text, not Markdown |
breaking |
boolean | no | Marks the release in the rail instead of filing it apart |
The body is the release note itself, written under ### headings (Added, Fixed, and so on).
The version chip beside the wordmark in the header is read from the newest entry here, which is
why there is no version field in the config to forget.
plans
src/content/plans.json, an array. Each object is a column on /pricing and a block on the home
page.
| Field | Type | Required | Meaning |
|---|---|---|---|
id |
string | yes | The key. Not printed |
name |
string | yes | Plan name |
tagline |
string | yes | One line under the name |
priceMonthly |
number | yes | Per month, in currency |
priceYearly |
number | yes | Per year. The saving against twelve months is computed |
currency |
string | no | ISO code, USD by default. Formatted with Intl for site.locale |
features |
string[] | yes | Printed in the plan column. At least one |
matrix |
object | no | Rows of the comparison table: label → true, false or a string |
cta |
{ label, href } |
yes | The button |
featured |
boolean | no | Prints “Most bought” over the column and makes its button the solid one. Set it on one plan |
order |
integer > 0 | yes | Left to right |
matrix keys are shared across plans on purpose: the table prints the union of them, in the order
the plans list them. A plan that does not mention a row prints an em dash; false prints No.
blog
src/content/blog/<slug>.md or .mdx. The filename is the URL: /blog/<slug>.
| Field | Type | Required | Meaning |
|---|---|---|---|
title |
string | yes | |
description |
string | yes | The row on the index, the meta description, the feed |
pubDate |
date | yes | Sort order, newest first |
updatedDate |
date | no | A second line under the byline |
tags |
string[] | no | Each tag gets a page at /blog/tag/<tag>; the slug is generated |
author |
string | no | The name as it should be printed. Falls back to site.author in the JSON-LD |
series |
string | no | Posts carrying the same string are one series |
draft |
boolean | no | true keeps the post out of the build, in dev as well |
author is a plain string rather than a reference, so a name with no entry in authors.json
still prints its byline. A series has no part numbers: the parts are ordered by publication date,
which cannot disagree with itself the way two hand-typed numbers can. A draft part is not in the
list, so a series that announces two parts delivers two.
authors
src/content/authors.json, keyed by a slug of your choosing. The match with a post is on name,
not on the key.
| Field | Type | Required | Meaning |
|---|---|---|---|
name |
string | yes | Exactly as the post’s author writes it |
role |
string | yes | Printed under the post, beside the name |
url |
string | no | Absent: the name is set, not linked |
Add a person by adding an entry. Remove one and the byline keeps printing the name; the block at the foot of the post is what disappears.
What is computed, and therefore not a field
Reading time (from the body), the group number in the sidebar, the yearly saving, the previous and next documentation page, the parts of a series and their numbering, the related posts, the tag counts and every URL. Do not add a field for any of them.
Math
$…$ sets a formula inline and $$…$$ on its own lines, in posts, docs pages and changelog entries,
.md and .mdx alike. KaTeX turns it into HTML at build time, so the page ships no script for it,
and only a page with math loads the KaTeX stylesheet. Chemical equations use mhchem:
$\ce{2H2 + O2 -> 2H2O}$.
Two dollar signs in one paragraph ($20 and $30) read as a formula between them. Write \$ for a
literal dollar sign.
In Pro, \label{eq:name} in a $$…$$ formula numbers it, and $\eqref{eq:name}$ anywhere on the
page prints (1) as a link to it ($\ref{eq:name}$ prints the bare number). A reference may come
before its equation. Copying a formula copies its LaTeX source.
For a statement and its proof, import Theorem in an .mdx file:
<Theorem kind="definition" title="Optional name">…</Theorem>. kind is theorem, lemma,
proposition, corollary, definition, example, remark or proof. The first six share one
count per page; a proof ends with ∎. The words are ui.statements in src/config.ts.
MDX
A .md file is prose. Use .mdx when the page needs a callout, a set of language tabs or a
parameter table; the components are imported at the top of the file, and
customization.md lists them with their props.
Two things to know before you write one:
pnpm formatskipssrc/content/**/*.mdx. Prettier’s Markdown printer reflows a fenced block that sits inside a JSX element, which turns a tab full of shell into one line of it.- A fence inside
<Fragment slot="…">keeps its indentation. Indent it with the element; it reads better and nothing breaks.
Adding a field
Edit the zod schema in src/content.config.ts. A new field must be .optional() or carry a
.default(), or every existing file in that collection fails validation until it is edited.
pnpm check is what tells you.
Next: Customization