Quick start
Five minutes from nothing to a site that reloads as you type.
Two commands
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:
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:
Pointing one level too high still builds, which is what makes it worth saying out loud. It just builds the wrong site:
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:
A worked example
A repository laid out as content/ (org files), theme/ (unrelated), build.py:
# What is actually in there, before trusting anything with it.
# Build it somewhere disposable and look.
# Happy with it? Build for real, failing on broken links.
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:
Change the design
Everything visual lives in templates/base.html. It is an ordinary minijinja (Jinja2) template, and replacing it replaces the whole layout:
{{ page.title }} — {{ site.title }}
{% for item in nav %}{{ item.title }}{% endfor %}
{{ page.title }}
{{ body | safe }}
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
--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.