krz/orgo
Lightning fast org-mode static site generator.
clone: git clone https://gitbay.org/krz/orgo.git
v0.20.1: docs/guide/02-configuration.org · raw
1#+TITLE: Configuration
2#+DESCRIPTION: Every setting in orgo.toml, what it changes, and what it costs.
3#+LEDE: All of it optional. A missing config is a valid config.
4
5orgo looks for =orgo.toml= in the source directory. Pass a different path with
6=--config=. Every field has a default, so a directory of org files with no config still
7builds a complete site.
8
9A *missing* config is normal and silent. A *malformed* one is an error, and an unknown
10key is rejected by name — a misspelled setting that silently does nothing is how people
11lose an afternoon.
12
13* The whole file
14
15#+BEGIN_SRC toml
16[site]
17title = "orgo site"
18base_url = ""
19description = ""
20language = "en"
21
22[nav]
23mode = "top-level"
24# pages = ["index.org", "about.org"]
25
26[templates]
27dir = "templates"
28expose_page_list = false
29
30[highlight]
31theme = "InspiredGitHub"
32syntaxes_dir = "syntaxes"
33
34[build]
35drafts = false
36assets = []
37sitemap = true
38
39[html]
40heading_offset = 1
41toc = true
42section_numbers = false
43#+END_SRC
44
45Plus any number of =[[collections]]= blocks, documented in [[file:03-collections.org][Collections]].
46
47* [site]
48
49| Key | Default | Meaning |
50|-----+---------+---------|
51| =title= | ="orgo site"= | Site name. Available as ={{ site.title }}=. |
52| =base_url= | ="" | Absolute origin, *no trailing slash*. |
53| =description= | ="" | Available as ={{ site.description }}=. |
54| =language= | ="en"= | Goes in =<html lang>= in the built-in layout. |
55
56** base_url
57
58Leave it empty and the site is built entirely with relative URLs, which means it works
59from a subdirectory, from a filesystem path, and from any origin. That portability is why
60it is the default.
61
62Set it when you need absolute URLs, which two things require: *feeds*, because a feed is
63read away from the site that served it, and *canonical links*. The =absolute= template
64filter turns a site-root-relative path into a full URL, and errors if there is no base
65URL to build one from — rather than quietly emitting a relative URL that would make the
66feed invalid everywhere while looking fine.
67
68A trailing slash is rejected, because =https://example.com/= plus =blog/x.html= is
69=https://example.com//blog/x.html=.
70
71* [nav]
72
73The navigation shared by every page.
74
75| =mode= | Includes |
76|--------+----------|
77| ="top-level"= (default) | Pages at the site root. |
78| ="all"= | Every page. |
79| ="explicit"= | Only =nav.pages=, in the order listed. |
80| ="none"= | Nothing. |
81
82*"all" makes output quadratic.* Each of /n/ pages carries /n/ links, so total output
83grows with the square of the site. On a 1,790-page site that was 284 MB of mostly
84navigation. It is fine for a handful of pages and a trap beyond that.
85
86="top-level"= keeps the nav a map of the site's top level rather than an index of its
87contents, so nav size does not depend on how much you write.
88
89** explicit
90
91#+BEGIN_SRC toml
92[nav]
93mode = "explicit"
94pages = ["index.org", "about.org", "uses.org"]
95#+END_SRC
96
97Paths are *source* paths relative to the source root, and the order given is the order
98rendered — a hand-written nav is a designed sequence, not an alphabetical one. Naming a
99page that does not exist is an error, because a silently shorter nav is a poor way to
100learn about a typo.
101
102** Section landing pages in the nav
103
104If your sections live in subdirectories, none of them are top-level pages. Put the
105section's *generated* index in the nav instead, with =nav = true= on its collection —
106that is the page a nav entry should point at anyway.
107
108A generated page has no source file, so name it in =pages= by its *output* path:
109
110#+BEGIN_SRC toml
111[nav]
112mode = "explicit"
113pages = ["blog/index.html", "garden/index.html", "about.org"]
114#+END_SRC
115
116That is the only way to interleave the two: a collection that sets =nav = true= without
117being listed is appended after everything you did list, so ="about.org"= alone would put
118=About= first and the sections after it. Listing all of them puts each exactly where you
119said. Either spelling works for an authored page too — its source path or its output
120path — though the source path is the one that survives a =#+SLUG:=.
121
122* [[pages]]
123
124Which layout a page renders through. Without any of these, every authored page uses
125=base.html=.
126
127#+BEGIN_SRC toml
128[[pages]]
129match = "blog"
130template = "post.html"
131#+END_SRC
132
133=match= is a *source* path relative to the source root — a directory, covering every page
134beneath it however deep, or one =.org= file. It is matched by path component, so =blog=
135covers =blog/2026/post.org= and does not touch =blogroll.org=.
136
137A section's layout is a property of the section, which is why this is a rule and not
138something you write in each file: a blog post carries the same byline and reply footer as
139every other one, and repeating that in 200 files means maintaining one fact 200 times.
140
141** Which rule wins
142
143Most specific, by path depth — =blog/notes= beats =blog=, whatever order they appear in.
144An empty =match= covers the whole site, which is how you rename the default layout.
145
146A page that differs from its section says so itself, and that wins over any rule:
147
148#+BEGIN_SRC org
149,#+TITLE: Colophon
150,#+TEMPLATE: wide.html
151#+END_SRC
152
153Naming a template that is not in the templates directory is an error that names the page,
154the template and what does exist — a layout typo should not be a hunt.
155
156* [templates]
157
158| Key | Default | Meaning |
159|-----+---------+---------|
160| =dir= | ="templates"= | Directory of templates, relative to the source root. |
161| =expose_page_list= | =false= | Give every template a =pages= list of all page metadata. |
162
163** expose_page_list costs incremental precision
164
165With it on, any page can read every page's metadata — so adding one page can change any
166page's output, and the whole site must re-render on every add, rename or retitle. That is
167the trade for building an index by hand in a template. Most people want a
168[[file:03-collections.org][collection]] instead, which gets the same result while keeping
169adding a post a one-page rebuild.
170
171* [highlight]
172
173| Key | Default | Meaning |
174|-----+---------+---------|
175| =theme= | ="InspiredGitHub"= | A syntect theme name. |
176| =syntaxes_dir= | ="syntaxes"= | Extra =.sublime-syntax= files. |
177
178Any theme syntect ships: =InspiredGitHub=, =Solarized (dark)=, =Solarized (light)=,
179=base16-ocean.dark=, =base16-ocean.light=, =base16-eighties.dark=, =base16-mocha.dark=.
180An unknown name is an error listing the valid ones.
181
182Highlighting emits *CSS classes*, never inline styles, so themes live in a stylesheet.
183Each build writes =syntax.css= into the output and every page links it.
184
185orgo bundles TOML and Org on top of syntect's built-in languages. Anything else
186missing is a file away: put a =.sublime-syntax= definition in =syntaxes_dir= and it is
187loaded. A definition that fails to parse is reported and skipped, because one bad file
188should not stop a site from building.
189
190* [build]
191
192| Key | Default | Meaning |
193|-----+---------+---------|
194| =drafts= | =false= | Include pages marked =#+DRAFT:=. |
195| =assets= | =[]= | Extra directories copied to the *site root*. |
196| =sitemap= | =true= | Write =sitemap.xml=. Needs =site.base_url=. |
197
198=--drafts= on the command line turns this on for one run. The flag can only turn drafts
199on; it never turns off a config that asked for them.
200
201** sitemap.xml
202
203Every page the build emits, generated ones included — a crawler has no other way to learn
204that =/blog/= exists. =lastmod= is the page's own =#+DATE:= where it has one, and absent
205where it does not: a filesystem timestamp would say the day you cloned the repository.
206
207*Nothing is written until =site.base_url= is set.* A sitemap has nowhere to put a relative
208URL, so a zero-config build produces no sitemap rather than an invalid one. Set a base URL
209and it appears; set =sitemap = false= and it does not.
210
211** Static files that live elsewhere
212
213A site's static files do not always sit where its writing does. weblorg publishes
214=theme/static/= at =/=, and a repository migrating from it should not have to move
215=robots.txt= next to its blog posts to keep the URL:
216
217#+BEGIN_SRC toml
218[build]
219assets = ["../theme/static"]
220#+END_SRC
221
222Paths are relative to the source root and may point outside it. Each directory's
223*contents* land at the site root — =theme/static/img/logo.svg= publishes at =/img/logo.svg=,
224not =/static/img/logo.svg=.
225
226Two files claiming one URL is a build error naming both, rather than a coin flip decided
227by directory order. A path that is not a directory is an error too, since it is a typo.
228
229Under =watch= and =serve= these directories are watched as well, so editing a stylesheet
230outside the source tree still reloads the page.
231
232* [html]
233
234| Key | Default | Meaning |
235|-----+---------+---------|
236| =heading_offset= | =1= | Added to every org heading level. |
237| =toc= | =true= | Make =page.toc= available to templates. |
238| =section_numbers= | =false= | Number headings =1.=, =1.1.=, … |
239
240** heading_offset
241
242A level-1 org heading renders as =<h2>= by default, because the layout supplies the page
243title as the =<h1>=. This matches Emacs, whose =org-html-toplevel-hlevel= is 2 for the
244same reason.
245
246Set it to =0= if your template renders no title of its own — otherwise the document
247starts at =<h2>= with nothing above it.
248
249** section_numbers differs from Emacs on purpose
250
251=org-export-with-section-numbers= is on in Emacs, so an org-published site inherits
252numbered headings whether or not anyone chose them. Most sites do not want them, so the
253default here is the taste rather than the inheritance. Turning it on emits Emacs' own
254=section-number-N= classes.
255
256* Per-file overrides
257
258Org's own =#+OPTIONS:= switches override the site setting for one document:
259
260#+BEGIN_SRC org
261,#+OPTIONS: toc:nil num:t
262#+END_SRC
263
264| Switch | Overrides |
265|--------+-----------|
266| =toc:nil= / =toc:t= | =[html] toc= |
267| =num:t= / =num:nil= | =[html] section_numbers= |
268
269Off is spelled =nil=, =false=, =no=, =0= or =off=; anything else is on.
270
271* Configuration is a cache input
272
273The resolved config is hashed into every page's render key, so editing =orgo.toml=
274re-renders exactly the pages it affects — which for most settings is all of them. You
275never need =--no-cache= after a config change.