Getting Started

A site is a directory. It holds Markdown in content/, an optional Site.toml, and nothing else that matters:

my-site/
  Site.toml          identity and branding — optional
  content/           the Markdown
  deploy/            generated output — never edit by hand

A site with no Site.toml still builds. Every field has a defensible default, and demanding ceremony before the first page renders is its own kind of tax.

Building

site build

Reads content/ and writes deploy/. With no arguments it uses the working directory, so running it inside a site does the obvious thing.

It also takes a path, which is the part that matters once there is more than one site:

site build ~/sites/other

Every path the builder touches is derived from that directory, once, and absolutely. Nothing reads the working directory afterwards, which is why building a site you are not standing in works at all.

Previewing

site serve --open

Builds, serves deploy/ on http://127.0.0.1:8080/, and rebuilds whenever anything in content/ or Site.toml changes. The server is in-process — there is no Python, no Node, and no child process to outlive its parent and sit on the port.

The watcher re-reads the manifest on every pass, so editing a colour or a tagline takes effect without a restart. A broken manifest or a failed build keeps the last good site up and prints why:

site: change detected, rebuilding…
site: rebuild failed — fix and save again:
Site.toml is not valid: …

--port moves it, --bind 0.0.0.0 exposes it to the rest of your network (a phone, for testing), and --no-watch builds once and just serves.

The whole surface

CommandWhat it does
site buildcontent in, HTML out
site servebuild, serve, rebuild on change
site checkaudit the built output — links, secrets, SEO
site pushbuild, audit, and send it wherever [deploy] says
site pullmirror the live site back down
site versionwhich build made this

Every one of them takes the same site directory argument and the same --input / --output overrides, so a flag learned once works everywhere.

The manifest

Site.toml holds what varies between sites and cannot be Swift:

[site]
url      = "https://example.com"
language = "en-US"

[brand]
name      = "Example"
tagline   = "Tools, systems, and the occasional essay."
copyright = "© 2026 Example"

[theme]
mode = "auto"

[content]
# Not published at all, by path under content/.
withhold = ["book.okf/front-matter/title-page.md"]
# Published, but not advertised anywhere.
unlisted = ["book.okf"]

Structure is deliberately absent from that list. It lives in Swift — see Site Anatomy — because a section is a type there, and the compiler checks it.