docs/quickstart.org
226 lines · 7028 bytes
Quick start
- Two commands
- Adapting it to org files you already have
- Write a page
- Change the design
- Add a blog index
- Build for real
- Next
Two commands
orgo init my-site
orgo serve my-site -o _site
Open 127.0.0.1:3000. Edit my-site/index.org in your editor, save, and the page reloads
on its own.
init writes only files that do not already exist, so running it inside a directory that
already has content is safe and additive.
What init created
my-site/
orgo.toml every setting, commented — all at their defaults but `theme`
index.org the home page
blog/first-post.org a post, to show the collection working
templates/
base.html the page layout — edit this
list.html the blog index
tags.html the tag index
feed.xml an RSS feed
Adapting it to org files you already have
You do not need init, a config file, or templates. Every command takes the same two
paths:
orgo serve <SOURCE> -o <OUTPUT>
SOURCE is the URL root
This is the one thing worth getting right, and it is not "the project directory" — it is
the directory whose contents should sit at the top of your site. A file at
SOURCE/blog/post.org is published at /blog/post.html.
So if your writing lives in a content/ subdirectory, point at content/, not at the
repository around it:
cd ~/my-site
orgo serve content -o _site # → /blog/post.html
Pointing one level too high still builds, which is what makes it worth saying out loud. It just builds the wrong site:
orgo serve . -o _site # → /content/blog/post.html
Every URL gains a /content/ prefix, and every non-org file in the repository —
README.md, build scripts, licence files — is copied into the output as a site asset.
If you see either symptom, you picked the directory above the one you meant.
Common layouts
| Your files | Command |
|---|---|
~/notes/*.org |
orgo serve ~/notes -o /tmp/notes-site |
my-site/content/**/*.org |
cd my-site && orgo serve content -o _site |
my-site/*.org at the top level |
cd my-site && orgo serve . -o _site |
| Org files scattered in a code repo | Do not. Copy or symlink the ones you publish into one directory. |
OUTPUT can live inside the source
orgo serve . -o _site is fine: the output directory is recognised and skipped, so
the build never copies its own output back into itself. Nothing dot-prefixed is published
either, so .git stays out of a site built from a repository root.
Where config and templates go
Both live in the source directory — SOURCE/orgo.toml and SOURCE/templates/ — and
neither is published. If you would rather keep the config elsewhere, name it:
orgo serve content -o _site --config config/orgo.toml
A worked example
A repository laid out as content/ (org files), theme/ (unrelated), build.py:
cd ~/my-site
# What is actually in there, before trusting anything with it.
orgo audit content
# Build it somewhere disposable and look.
orgo serve content -o /tmp/preview
# Happy with it? Build for real, failing on broken links.
orgo build content -o _site --strict
The audit reports which org constructs appear, how often, and whether each is supported — names, counts and line numbers only, never your text. See Auditing a corpus.
Nothing in your files has to change. With no config you get a complete site: a built-in layout, navigation across your top-level pages, and syntax highlighting.
Write a page
Any .org file under the source directory becomes a page at the matching path.
notes/rust/borrowing.org becomes notes/rust/borrowing.html.
#+TITLE: Borrowing
#+DATE: <2026-02-02 Mon>
#+FILETAGS: :rust:notes:
#+DESCRIPTION: How the borrow checker thinks about lifetimes.
An opening paragraph, which becomes the excerpt in listings when there is no
description.
* A heading
Ordinary org: *bold*, /italic/, ~code~, [[https://orgmode.org][links]], and lists.
#+BEGIN_SRC rust
fn main() {}
#+END_SRC
The keywords are all optional. #+TITLE: names the page, #+DATE: orders it in
listings, #+FILETAGS: groups it on tag pages, and #+DESCRIPTION: is its summary.
Control the URL
By default the filename decides the URL. #+SLUG: overrides it, which is how a
date-prefixed filename becomes a clean address:
#+TITLE: Borrowing
#+SLUG: borrowing-explained
2026-02-02-borrowing.org now publishes as borrowing-explained.html.
Keep something unfinished out of the build
#+DRAFT: t
The page is not written, and does not appear in listings or navigation. Preview it while
you work with --drafts:
orgo serve my-site -o _site --drafts
Change the design
Everything visual lives in templates/base.html. It is an ordinary
minijinja (Jinja2) template, and replacing it replaces the
whole layout:
<!DOCTYPE html>
<html lang="{{ site.language }}">
<head>
<meta charset="utf-8">
<title>{{ page.title }} — {{ site.title }}</title>
<link rel="stylesheet" href="{{ root }}style.css">
</head>
<body>
<nav>{% for item in nav %}<a href="{{ item.url }}">{{ item.title }}</a>{% endfor %}</nav>
<h1>{{ page.title }}</h1>
{{ body | safe }}
</body>
</html>
root is the ../ prefix back to the site root, so the same template works at any
depth. Any other file in the source directory — style.css, images, fonts — is copied to
the output untouched.
Editing a template rebuilds every page that uses it, so the browser reloads while you are still looking at it. The full list of variables is in Templates.
Add a blog index
Listing pages have no source file; they are declared in orgo.toml:
[[collections]]
source = "blog"
output = "blog/index.html"
template = "list.html"
title = "Blog"
sort = "date"
order = "desc"
nav = true
That is also how you get tag pages, pagination and an RSS feed — same mechanism, more settings. See Collections.
Build for real
orgo build my-site -o _site --strict
--strict turns broken internal links and parse diagnostics into a non-zero exit, which
is what you want in CI. Deployment is just copying _site somewhere; see
Deploying.
Next
- Command reference — every command and flag.
- Configuration — every setting in
orgo.toml. - Org support — exactly which org syntax is handled.