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
| Command | What it does |
|---|---|
site build | content in, HTML out |
site serve | build, serve, rebuild on change |
site check | audit the built output — links, secrets, SEO |
site push | build, audit, and send it wherever [deploy] says |
site pull | mirror the live site back down |
site version | which 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.