krz/orgo

Lightning fast org-mode static site generator.

clone: git clone https://gitbay.org/krz/orgo.git

v0.21.0: 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"
 21theme = ""
 22
 23[nav]
 24mode = "top-level"
 25# pages = ["index.org", "about.org"]
 26
 27[templates]
 28dir = "templates"
 29expose_page_list = false
 30
 31[highlight]
 32theme = "InspiredGitHub"
 33syntaxes_dir = "syntaxes"
 34
 35[build]
 36drafts = false
 37assets = []
 38sitemap = true
 39
 40[html]
 41heading_offset = 1
 42toc = true
 43section_numbers = false
 44#+END_SRC
 45
 46Plus any number of =[[collections]]= blocks, documented in [[file:03-collections.org][Collections]].
 47
 48* [site]
 49
 50| Key | Default | Meaning |
 51|-----+---------+---------|
 52| =title= | ="orgo site"= | Site name. Available as ={{ site.title }}=. |
 53| =base_url= | ="" | Absolute origin, *no trailing slash*. |
 54| =description= | ="" | Available as ={{ site.description }}=. |
 55| =language= | ="en"= | Goes in =<html lang>= in the built-in layout. |
 56| =theme= | ="" | A built-in stylesheet, written to the output as =theme.css=. |
 57
 58** theme
 59
 60Four themes are compiled into the binary. Name one and each build writes it to the
 61output root as =theme.css=, which the built-in layout and the templates =orgo init=
 62writes both link.
 63
 64| Theme | Shape | For |
 65|-------+-------+-----|
 66| ="plain"= | Narrow, system fonts, hairline rules. | Readable defaults to build your own CSS on. |
 67| ="blog"= | Serif prose, a centred masthead, styled post lists. | Dated writing. |
 68| ="wiki"= | Wide and dense, contents in the margin, TODO states as badges. | Notes, a reference site. |
 69| ="docs"= | Narrow, a contents card, quote blocks as notes, =#+LEDE:=. | A guide read in order. |
 70
 71All four follow =prefers-color-scheme=, so a site gets a dark mode without a toggle, a
 72setting or a line of JavaScript — and all four reflow from a 320px phone up, with tables
 73and code blocks scrolling inside their own box rather than widening the page.
 74
 75The default is empty: no stylesheet is written and no page links one, so the output is
 76unstyled HTML. That is deliberate — a site that already ships CSS of its own should not
 77find a second stylesheet competing with it, and upgrading orgo should never restyle a
 78site. An unknown name is an error listing the four.
 79
 80A theme styles the markup orgo already emits — headings, tags, TODO keywords, checkbox
 81lists, footnotes, tables — plus the chrome the built-in layout puts around it. There is
 82no theme-specific HTML, so switching or removing one touches no template.
 83
 84Every colour is a custom property on =:root=, named =--orgo-*=. To adjust rather than
 85replace a theme, ship a stylesheet of your own as an asset, link it after =theme.css=,
 86and redefine the handful you care about:
 87
 88#+BEGIN_SRC css
 89:root {
 90  --orgo-accent: #7a1fa2;
 91  --orgo-measure: 46rem;
 92}
 93#+END_SRC
 94
 95Code /blocks/ are the one part a theme cannot make follow =prefers-color-scheme=:
 96=syntax.css= is generated from a single syntect theme, so a theme's dark mode keeps its
 97block surface light to stay readable against the default =InspiredGitHub=. Pair a dark
 98=highlight.theme= with overrides of =--orgo-code-bg=, =--orgo-code-fg= and
 99=--orgo-code-rule=. Those three colour blocks only — inline =~code~= follows the page's
100own scheme, so it stays legible whichever highlight theme you use. The documentation site
101does exactly this; its =style.css= is those three lines and nothing else.
102
103When you outgrow a theme, drop =theme= from the config and write =templates/base.html=
104against your own CSS. Nothing else changes.
105
106** base_url
107
108Leave it empty and the site is built entirely with relative URLs, which means it works
109from a subdirectory, from a filesystem path, and from any origin. That portability is why
110it is the default.
111
112Set it when you need absolute URLs, which two things require: *feeds*, because a feed is
113read away from the site that served it, and *canonical links*. The =absolute= template
114filter turns a site-root-relative path into a full URL, and errors if there is no base
115URL to build one from — rather than quietly emitting a relative URL that would make the
116feed invalid everywhere while looking fine.
117
118A trailing slash is rejected, because =https://example.com/= plus =blog/x.html= is
119=https://example.com//blog/x.html=.
120
121* [nav]
122
123The navigation shared by every page.
124
125| =mode= | Includes |
126|--------+----------|
127| ="top-level"= (default) | Pages at the site root. |
128| ="all"= | Every page. |
129| ="explicit"= | Only =nav.pages=, in the order listed. |
130| ="none"= | Nothing. |
131
132*"all" makes output quadratic.* Each of /n/ pages carries /n/ links, so total output
133grows with the square of the site. On a 1,790-page site that was 284 MB of mostly
134navigation. It is fine for a handful of pages and a trap beyond that.
135
136="top-level"= keeps the nav a map of the site's top level rather than an index of its
137contents, so nav size does not depend on how much you write.
138
139** explicit
140
141#+BEGIN_SRC toml
142[nav]
143mode = "explicit"
144pages = ["index.org", "about.org", "uses.org"]
145#+END_SRC
146
147Paths are *source* paths relative to the source root, and the order given is the order
148rendered — a hand-written nav is a designed sequence, not an alphabetical one. Naming a
149page that does not exist is an error, because a silently shorter nav is a poor way to
150learn about a typo.
151
152** Section landing pages in the nav
153
154If your sections live in subdirectories, none of them are top-level pages. Put the
155section's *generated* index in the nav instead, with =nav = true= on its collection —
156that is the page a nav entry should point at anyway.
157
158A generated page has no source file, so name it in =pages= by its *output* path:
159
160#+BEGIN_SRC toml
161[nav]
162mode = "explicit"
163pages = ["blog/index.html", "garden/index.html", "about.org"]
164#+END_SRC
165
166That is the only way to interleave the two: a collection that sets =nav = true= without
167being listed is appended after everything you did list, so ="about.org"= alone would put
168=About= first and the sections after it. Listing all of them puts each exactly where you
169said. Either spelling works for an authored page too — its source path or its output
170path — though the source path is the one that survives a =#+SLUG:=.
171
172* [[pages]]
173
174Which layout a page renders through. Without any of these, every authored page uses
175=base.html=.
176
177#+BEGIN_SRC toml
178[[pages]]
179match = "blog"
180template = "post.html"
181#+END_SRC
182
183=match= is a *source* path relative to the source root — a directory, covering every page
184beneath it however deep, or one =.org= file. It is matched by path component, so =blog=
185covers =blog/2026/post.org= and does not touch =blogroll.org=.
186
187A section's layout is a property of the section, which is why this is a rule and not
188something you write in each file: a blog post carries the same byline and reply footer as
189every other one, and repeating that in 200 files means maintaining one fact 200 times.
190
191** Which rule wins
192
193Most specific, by path depth — =blog/notes= beats =blog=, whatever order they appear in.
194An empty =match= covers the whole site, which is how you rename the default layout.
195
196A page that differs from its section says so itself, and that wins over any rule:
197
198#+BEGIN_SRC org
199,#+TITLE: Colophon
200,#+TEMPLATE: wide.html
201#+END_SRC
202
203Naming a template that is not in the templates directory is an error that names the page,
204the template and what does exist — a layout typo should not be a hunt.
205
206* [templates]
207
208| Key | Default | Meaning |
209|-----+---------+---------|
210| =dir= | ="templates"= | Directory of templates, relative to the source root. |
211| =expose_page_list= | =false= | Give every template a =pages= list of all page metadata. |
212
213** expose_page_list costs incremental precision
214
215With it on, any page can read every page's metadata — so adding one page can change any
216page's output, and the whole site must re-render on every add, rename or retitle. That is
217the trade for building an index by hand in a template. Most people want a
218[[file:03-collections.org][collection]] instead, which gets the same result while keeping
219adding a post a one-page rebuild.
220
221* [highlight]
222
223| Key | Default | Meaning |
224|-----+---------+---------|
225| =theme= | ="InspiredGitHub"= | A syntect theme name. |
226| =syntaxes_dir= | ="syntaxes"= | Extra =.sublime-syntax= files. |
227
228Any theme syntect ships: =InspiredGitHub=, =Solarized (dark)=, =Solarized (light)=,
229=base16-ocean.dark=, =base16-ocean.light=, =base16-eighties.dark=, =base16-mocha.dark=.
230An unknown name is an error listing the valid ones.
231
232Highlighting emits *CSS classes*, never inline styles, so themes live in a stylesheet.
233Each build writes =syntax.css= into the output and every page links it.
234
235orgo bundles TOML and Org on top of syntect's built-in languages. Anything else
236missing is a file away: put a =.sublime-syntax= definition in =syntaxes_dir= and it is
237loaded. A definition that fails to parse is reported and skipped, because one bad file
238should not stop a site from building.
239
240* [build]
241
242| Key | Default | Meaning |
243|-----+---------+---------|
244| =drafts= | =false= | Include pages marked =#+DRAFT:=. |
245| =assets= | =[]= | Extra directories copied to the *site root*. |
246| =sitemap= | =true= | Write =sitemap.xml=. Needs =site.base_url=. |
247
248=--drafts= on the command line turns this on for one run. The flag can only turn drafts
249on; it never turns off a config that asked for them.
250
251** sitemap.xml
252
253Every page the build emits, generated ones included — a crawler has no other way to learn
254that =/blog/= exists. =lastmod= is the page's own =#+DATE:= where it has one, and absent
255where it does not: a filesystem timestamp would say the day you cloned the repository.
256
257*Nothing is written until =site.base_url= is set.* A sitemap has nowhere to put a relative
258URL, so a zero-config build produces no sitemap rather than an invalid one. Set a base URL
259and it appears; set =sitemap = false= and it does not.
260
261** Static files that live elsewhere
262
263A site's static files do not always sit where its writing does. weblorg publishes
264=theme/static/= at =/=, and a repository migrating from it should not have to move
265=robots.txt= next to its blog posts to keep the URL:
266
267#+BEGIN_SRC toml
268[build]
269assets = ["../theme/static"]
270#+END_SRC
271
272Paths are relative to the source root and may point outside it. Each directory's
273*contents* land at the site root — =theme/static/img/logo.svg= publishes at =/img/logo.svg=,
274not =/static/img/logo.svg=.
275
276Two files claiming one URL is a build error naming both, rather than a coin flip decided
277by directory order. A path that is not a directory is an error too, since it is a typo.
278
279Under =watch= and =serve= these directories are watched as well, so editing a stylesheet
280outside the source tree still reloads the page.
281
282* [html]
283
284| Key | Default | Meaning |
285|-----+---------+---------|
286| =heading_offset= | =1= | Added to every org heading level. |
287| =toc= | =true= | Make =page.toc= available to templates. |
288| =section_numbers= | =false= | Number headings =1.=, =1.1.=, … |
289
290** heading_offset
291
292A level-1 org heading renders as =<h2>= by default, because the layout supplies the page
293title as the =<h1>=. This matches Emacs, whose =org-html-toplevel-hlevel= is 2 for the
294same reason.
295
296Set it to =0= if your template renders no title of its own — otherwise the document
297starts at =<h2>= with nothing above it.
298
299** section_numbers differs from Emacs on purpose
300
301=org-export-with-section-numbers= is on in Emacs, so an org-published site inherits
302numbered headings whether or not anyone chose them. Most sites do not want them, so the
303default here is the taste rather than the inheritance. Turning it on emits Emacs' own
304=section-number-N= classes.
305
306* Per-file overrides
307
308Org's own =#+OPTIONS:= switches override the site setting for one document:
309
310#+BEGIN_SRC org
311,#+OPTIONS: toc:nil num:t
312#+END_SRC
313
314| Switch | Overrides |
315|--------+-----------|
316| =toc:nil= / =toc:t= | =[html] toc= |
317| =num:t= / =num:nil= | =[html] section_numbers= |
318
319Off is spelled =nil=, =false=, =no=, =0= or =off=; anything else is on.
320
321* Configuration is a cache input
322
323The resolved config is hashed into every page's render key, so editing =orgo.toml=
324re-renders exactly the pages it affects — which for most settings is all of them. You
325never need =--no-cache= after a config change.