#+TITLE: Configuration #+DESCRIPTION: Every setting in orgo.toml, what it changes, and what it costs. #+LEDE: All of it optional. A missing config is a valid config. orgo looks for =orgo.toml= in the source directory. Pass a different path with =--config=. Every field has a default, so a directory of org files with no config still builds a complete site. A *missing* config is normal and silent. A *malformed* one is an error, and an unknown key is rejected by name — a misspelled setting that silently does nothing is how people lose an afternoon. * The whole file #+BEGIN_SRC toml [site] title = "orgo site" base_url = "" description = "" language = "en" theme = "" [nav] mode = "top-level" # pages = ["index.org", "about.org"] [templates] dir = "templates" expose_page_list = false [highlight] theme = "InspiredGitHub" theme_dark = "" syntaxes_dir = "syntaxes" [build] drafts = false assets = [] sitemap = true [html] heading_offset = 1 toc = true section_numbers = false #+END_SRC Plus any number of =[[collections]]= blocks, documented in [[file:03-collections.org][Collections]]. * [site] | Key | Default | Meaning | |-----+---------+---------| | =title= | ="orgo site"= | Site name. Available as ={{ site.title }}=. | | =base_url= | ="" | Absolute origin, *no trailing slash*. | | =description= | ="" | Available as ={{ site.description }}=. | | =language= | ="en"= | Goes in == in the built-in layout. | | =theme= | ="" | A built-in stylesheet, written to the output as =theme.css=. | ** theme Four themes are compiled into the binary. Name one and each build writes it to the output root as =theme.css=, which the built-in layout and the templates =orgo init= writes both link. | Theme | Shape | For | |-------+-------+-----| | ="plain"= | Narrow, system fonts, hairline rules. | Readable defaults to build your own CSS on. | | ="blog"= | Serif prose, a centred masthead, styled post lists. | Dated writing. | | ="wiki"= | Wide and dense, contents in the margin, TODO states as badges. | Notes, a reference site. | | ="docs"= | Narrow, a contents card, quote blocks as notes, =#+LEDE:=. | A guide read in order. | All four follow =prefers-color-scheme=, so a site gets a dark mode without a toggle, a setting or a line of JavaScript — and all four reflow from a 320px phone up, with tables and code blocks scrolling inside their own box rather than widening the page. The default is empty: no stylesheet is written and no page links one, so the output is unstyled HTML. That is deliberate — a site that already ships CSS of its own should not find a second stylesheet competing with it, and upgrading orgo should never restyle a site. An unknown name is an error listing the four. A theme styles the markup orgo already emits — headings, tags, TODO keywords, checkbox lists, footnotes, tables — plus the chrome the built-in layout puts around it. There is no theme-specific HTML, so switching or removing one touches no template. Every colour is a custom property on =:root=, named =--orgo-*=. To adjust rather than replace a theme, ship a stylesheet of your own as an asset, link it after =theme.css=, and redefine the handful you care about: #+BEGIN_SRC css :root { --orgo-accent: #7a1fa2; --orgo-measure: 46rem; } #+END_SRC Code /blocks/ are the one part a built-in theme leaves light in dark mode: =syntax.css= is coloured by =highlight.theme=, and that default is a light theme. Two pieces make a block follow =prefers-color-scheme= — =highlight.theme_dark= for the tokens, and =--orgo-code-bg=, =--orgo-code-fg= and =--orgo-code-rule= for the surface under them. Set both, or neither and blocks stay light in both schemes. Those three properties colour blocks only — inline =~code~= follows the page's own scheme, so it stays legible whichever highlight theme you use. The documentation site keeps a dark surface in both schemes instead; its =style.css= is those three lines and nothing else. When you outgrow a theme, drop =theme= from the config and write =templates/base.html= against your own CSS. Nothing else changes. ** base_url Leave it empty and the site is built entirely with relative URLs, which means it works from a subdirectory, from a filesystem path, and from any origin. That portability is why it is the default. Set it when you need absolute URLs, which two things require: *feeds*, because a feed is read away from the site that served it, and *canonical links*. The =absolute= template filter turns a site-root-relative path into a full URL, and errors if there is no base URL to build one from — rather than quietly emitting a relative URL that would make the feed invalid everywhere while looking fine. A trailing slash is rejected, because =https://example.com/= plus =blog/x.html= is =https://example.com//blog/x.html=. * [nav] The navigation shared by every page. | =mode= | Includes | |--------+----------| | ="top-level"= (default) | Pages at the site root. | | ="all"= | Every page. | | ="explicit"= | Only =nav.pages=, in the order listed. | | ="none"= | Nothing. | *"all" makes output quadratic.* Each of /n/ pages carries /n/ links, so total output grows with the square of the site. On a 1,790-page site that was 284 MB of mostly navigation. It is fine for a handful of pages and a trap beyond that. ="top-level"= keeps the nav a map of the site's top level rather than an index of its contents, so nav size does not depend on how much you write. ** explicit #+BEGIN_SRC toml [nav] mode = "explicit" pages = ["index.org", "about.org", "uses.org"] #+END_SRC Paths are *source* paths relative to the source root, and the order given is the order rendered — a hand-written nav is a designed sequence, not an alphabetical one. Naming a page that does not exist is an error, because a silently shorter nav is a poor way to learn about a typo. ** Section landing pages in the nav If your sections live in subdirectories, none of them are top-level pages. Put the section's *generated* index in the nav instead, with =nav = true= on its collection — that is the page a nav entry should point at anyway. A generated page has no source file, so name it in =pages= by its *output* path: #+BEGIN_SRC toml [nav] mode = "explicit" pages = ["blog/index.html", "garden/index.html", "about.org"] #+END_SRC That is the only way to interleave the two: a collection that sets =nav = true= without being listed is appended after everything you did list, so ="about.org"= alone would put =About= first and the sections after it. Listing all of them puts each exactly where you said. Either spelling works for an authored page too — its source path or its output path — though the source path is the one that survives a =#+SLUG:=. * [[pages]] Which layout a page renders through. Without any of these, every authored page uses =base.html=. #+BEGIN_SRC toml [[pages]] match = "blog" template = "post.html" #+END_SRC =match= is a *source* path relative to the source root — a directory, covering every page beneath it however deep, or one =.org= file. It is matched by path component, so =blog= covers =blog/2026/post.org= and does not touch =blogroll.org=. A section's layout is a property of the section, which is why this is a rule and not something you write in each file: a blog post carries the same byline and reply footer as every other one, and repeating that in 200 files means maintaining one fact 200 times. ** Which rule wins Most specific, by path depth — =blog/notes= beats =blog=, whatever order they appear in. An empty =match= covers the whole site, which is how you rename the default layout. A page that differs from its section says so itself, and that wins over any rule: #+BEGIN_SRC org ,#+TITLE: Colophon ,#+TEMPLATE: wide.html #+END_SRC Naming a template that is not in the templates directory is an error that names the page, the template and what does exist — a layout typo should not be a hunt. * [templates] | Key | Default | Meaning | |-----+---------+---------| | =dir= | ="templates"= | Directory of templates, relative to the source root. | | =expose_page_list= | =false= | Give every template a =pages= list of all page metadata. | ** expose_page_list costs incremental precision With it on, any page can read every page's metadata — so adding one page can change any page's output, and the whole site must re-render on every add, rename or retitle. That is the trade for building an index by hand in a template. Most people want a [[file:03-collections.org][collection]] instead, which gets the same result while keeping adding a post a one-page rebuild. * [highlight] | Key | Default | Meaning | |-----+---------+---------| | =theme= | ="InspiredGitHub"= | A syntect theme name. | | =theme_dark= | =""= | A second theme for readers in dark mode. | | =syntaxes_dir= | ="syntaxes"= | Extra =.sublime-syntax= files. | Any theme syntect ships: =InspiredGitHub=, =Solarized (dark)=, =Solarized (light)=, =base16-ocean.dark=, =base16-ocean.light=, =base16-eighties.dark=, =base16-mocha.dark=. An unknown name is an error listing the valid ones. =theme_dark= is off by default, and one theme colours every reader. Name a second one and =syntax.css= carries both, each behind the =prefers-color-scheme= query it belongs to, so a reader's system picks the colours — one stylesheet, no JavaScript, nothing extra for a layout to link: #+BEGIN_SRC toml [highlight] theme = "InspiredGitHub" theme_dark = "base16-ocean.dark" #+END_SRC Only the token colours change with the scheme. The surface a block sits on is the page's, so give your dark mode a dark =pre= background — a dark theme's colours are chosen for one — with =--orgo-code-bg= under a built-in theme, or your own CSS. The two themes are separated rather than stacked because they name different scopes: a light theme's =.source.python .keyword= outranks a dark theme's =.keyword=, so appending one to the other would leave light colours on some tokens. The cost of the separation is that a browser too old to know =prefers-color-scheme= matches neither query and shows code unhighlighted. Leaving =theme_dark= empty keeps the unconditional rules of before. Highlighting emits *CSS classes*, never inline styles, so themes live in a stylesheet. Each build writes =syntax.css= into the output and every page links it. orgo bundles TOML and Org on top of syntect's built-in languages. Anything else missing is a file away: put a =.sublime-syntax= definition in =syntaxes_dir= and it is loaded. A definition that fails to parse is reported and skipped, because one bad file should not stop a site from building. * [build] | Key | Default | Meaning | |-----+---------+---------| | =drafts= | =false= | Include pages marked =#+DRAFT:=. | | =assets= | =[]= | Extra directories copied to the *site root*. | | =sitemap= | =true= | Write =sitemap.xml=. Needs =site.base_url=. | =--drafts= on the command line turns this on for one run. The flag can only turn drafts on; it never turns off a config that asked for them. ** sitemap.xml Every page the build emits, generated ones included — a crawler has no other way to learn that =/blog/= exists. =lastmod= is the page's own =#+DATE:= where it has one, and absent where it does not: a filesystem timestamp would say the day you cloned the repository. *Nothing is written until =site.base_url= is set.* A sitemap has nowhere to put a relative URL, so a zero-config build produces no sitemap rather than an invalid one. Set a base URL and it appears; set =sitemap = false= and it does not. ** Static files that live elsewhere A site's static files do not always sit where its writing does. weblorg publishes =theme/static/= at =/=, and a repository migrating from it should not have to move =robots.txt= next to its blog posts to keep the URL: #+BEGIN_SRC toml [build] assets = ["../theme/static"] #+END_SRC Paths are relative to the source root and may point outside it. Each directory's *contents* land at the site root — =theme/static/img/logo.svg= publishes at =/img/logo.svg=, not =/static/img/logo.svg=. Two files claiming one URL is a build error naming both, rather than a coin flip decided by directory order. A path that is not a directory is an error too, since it is a typo. Under =watch= and =serve= these directories are watched as well, so editing a stylesheet outside the source tree still reloads the page. * [html] | Key | Default | Meaning | |-----+---------+---------| | =heading_offset= | =1= | Added to every org heading level. | | =toc= | =true= | Make =page.toc= available to templates. | | =section_numbers= | =false= | Number headings =1.=, =1.1.=, … | ** heading_offset A level-1 org heading renders as =