krz/orgo

Lightning fast org-mode static site generator.

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

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