Configuration

Config format

The configuration file is in the simple TOML format. Sajt is using version 0.40 of TOML—although likely later versions of TOML will work just as fine, as sajt's use is of TOML's features is limited.

Sajt's config makes use of TOML sections, [<sectionName>]. Sections are used to represent folders or groups of files on-disk. In each TOML section, we configure site generation by setting a few different config keys in the section. Which keys are set and how to use them is documented throughout this page. Most config keys are optional and have well-defined defaults, but two keys are always strictly required:

[general]
files = ["index.md"]
template = "template.sajt.html"

A template is a bit like a picture frame, whereas you can think of input files from files like the picture being framed.

files is a list, and can contain many input files. When processing the list of files, each file is passed through the defined template.

On the section [general]

There is one special section, [general], and we usually refer to all the other types of sections as groups / a group section, or denoting it as a group placeholder with <groupName>.

The [general] section stores settings that will be applied to all evaluated files unless explicitly overridden. A group section can override any general settings by providing its own values for the keys set in [general]:

[general]
template = "sample-template.sajt.html"

[introduction]
# this section group will use the site-wide template `sample-template.sajt.html`
files = ["intro.md"]

[docs]
# override the site-wide template
template = "another-template.sajt.html"
files = ["documentation.md"]

Config keys are defined on either [general] or on [<groupName>] -- for an exhaustive list of config keys, see section Config keys.

Let's look at how a simple config might be written.

Simple config

Below we have a simple sajt config:

[general]
outroot = "outdir"
extras = ["extras/", "links/"]

[docs]
template = "sample-template.txt"
dirFiles = "./"
files = ["content.md"]

We'll save this in a file called simple-config.toml.

Here is the starting point, before running sajt, as a directory listing:

sample-template.txt
simple-config.toml
extras/
links/
docs/
    content.md

The data flow goes like

╔══════════════════╗   
║ docs/content.md  ║ ──passed to────┐
╚══════════════════╝                │
 ╔═════════════════════╗            │
 ║ sample-template.txt ║ ───────────┘
 ╚═════════════════════╝   
  │
  └───saved to─────┐
╔════════════════════╗
║ outdir/content.md  ║
╚════════════════════╝

The config applies template sample-template.txt to the input docs/content.md. Output is saved as outdir/content.md.

Any needed output directories will be created if necessary. Directories and files specified in extras are also copied to the output directory. Entries in extras are only copied - they are not applied to the template.

This is the directory listing after running sajt --config simple-config.toml:

sample-template.txt
simple-config.toml
docs/
    content.md
extras/
links/
outdir/
    extras/
    links/
    content.md

This example intends to clarify basic usage. There are more config keys that can be used, allowing us to specify the name of output files, toggle parsing of input as markdown and converting to HTML or keeping it unchanged, to rewrite input file's suffixes but keeping the rest of the filename (from .md to .html), and specify all manner of directories depending on a site's structure.

Setting up RSS

RSS feed generation can be enabled for any section of the config, allowing your site to announce any new files that are added to that section in a way that it is discoverable and compatible with any RSS reader.

To show how to set it up, we'll use the example from above, which described a docs section. We'll augment docs with the structure needed for the feed generation. We'll take the section name, append .rss to it, and add at least the RSS-specific config keys canonicalURL and title:

[general]
outroot = "outdir"
extras = ["extras/", "links/"]

[docs]
template = "sample-template.txt"
dirFiles = "./"
files = ["content.md"]
suffix = "html"

[docs.rss]
canonicalURL = "https://docs-site.com"
relativeURL = "docs/"
title = "sajt documentation"
description = "a feed sharing quarterly updates to sajt"

Note: suffix = "html", which sets the output file's extension to .html and is also picked up when creating the RSS feed.

Using the info we set above, our feed docs.xml would look like:

<rss version="2.0">
  <channel>
    <title>sajt documentation</title>
    <link>https://docs-site.com</link>
    <description>a feed sharing quarterly updates to sajt</description>
    <item>
        <title><![CDATA[content title]]></title>
        <link><![CDATA[https://docs-site.com/docs/content.md.html]]></link>
        <description><![CDATA[welcome to the docs site]]></description>
        <pubDate><![CDATA[Wed, 14 Oct 2025 19:13:37 +0200]]></pubDate>
    </item>
  </channel>
</rss>

The RSS generator picks up all the files described by [docs]'s key files, and it even works with the files = "*.txt" glob syntax.

Let's say we change files by adding installation.md:

files = ["content.md", "installation.md"] 

The next time we run sajt.com --config simple-config.toml, our feed would be updated:

<rss version="2.0">
  <channel>
    <title>sajt documentation</title>
    <link>https://docs-site.com</link>
    <description>a feed sharing quarterly updates to sajt</description>
    <item>
        <title><![CDATA[content title]]></title>
        <link><![CDATA[https://docs-site.com/docs/content.html]]></link>
        <description><![CDATA[welcome to the docs site]]></description>
        <pubDate><![CDATA[Wed, 14 Oct 2025 19:13:37 +0200]]></pubDate>
    </item>
    <item>
        <title><![CDATA[installation]]></title>
        <link><![CDATA[https://docs-site.com/docs/installation.html]]></link>
        <description><![CDATA[here is what to consider when it comes to installation]]></description>
        <pubDate><![CDATA[Wed, 15 Oct 2025 09:12:51 +0200]]></pubDate>
    </item>
  </channel>
</rss>

We can see that installation.md has been picked up and is represented as an html file.

If we're looking carefully, we can also note that the RSS entry for content.md has kept its publish date pubDate from the first generation. This is good, because if the older entries had their dates changed it would be a nuisance for anyone following the feed in their reader.

Config keys

Below is an exhaustive list of all the currently defined config keys.

Keys either operate on a sajt-specific section, called [general], or on user-defined sections, referred to as <groupName>.

Only two keys are required in a sajt config: template and files. All other keys are optional with their defaults behaviour, if omitted, documented below.

Instead of reading the entire list below, you may want to jump to section Config examples.

To keep this section compact, we use the convention of [general].key to signify setting key on the section general:

[general]
key = ...

Note: [general] is optional if other user-defined sections exist.

<shared keys>

The section <shared keys> enumerates keys that can be set on any section.

root Optional

The directory that is the root for all the inputs (templates, input files, or extras) and relative paths sajt tries to read and process.

Defaults: the directory hosting the config file is used if [general].root and [<groupName>].root are not provided.

outroot Optional

The directory that will be the root for the output written by sajt after applying template file to input files.

Defaults: value of whatever root is derived to be

extras Optional

A list of paths to additional files or folders that should be copied over to the specified outroot.

Defaults: empty table (list) as default

template Required!

The filename of the template that will be used in combination with an input file, resulting in another file that is saved as the output of the templating process.

This must be set either as [general].template or [<groupName>].template

dirFiles Optional

Used to source files from another part of the file system.

For example, a sajt-specific directory may contain all the HTML-specific files it needs (css, sajt template, fonts, website image elements). But the actual content files may be stored elsewhere, such as in a repo directory. By specifying dirFiles, we can pull in the content files separately from the rest of the project-specific files.

Defaults: uses value of [<groupName>] as directory.

NOTE: If key dirFiles is set in [general] then <groupName> WILL NOT be used

dirTemplate Optional

The parent folder of template files.

Defaults: value of whatever root is derived to be.

dirInput Optional

The parent folder of input files.

Defaults: value of whatever root is derived to be.

_note (2025-05-30): dirInput is used for templating mycelial tech across different group names_

dirOutput Optional

The directory in which to save output files. If dirOutput is an absolute path then that is used, otherwise dirOutput is relative to outroot as join(outroot, dirOutput).

Defaults: uses value of [<groupName>].

files Required!

A list that specifies the filenames to read as input files.

This list will be processed one at a time. The full path of any input files read is constructed as

path.join(root, dirInput, dirFiles, filename)

NOTE: If any path segment is absolute (e.g. dirFiles) then that overrides the any preceeding path segments.

This must be set either as [general].files or [<groupName>].files.

NOTE: Even if [general].files is the only one set of files provided, sajt will be able to read different files in different groups by virtue of setting/using dirInput / dirFiles to locate the input files to read.

parseInput Optional

Toggles whether input files should be parsed based on the input's file extension (.md for markdown or .gmi for gemini) and converted into HTML.

If parseInput = false the parsing is turned off. This can be useful in circumstances when you want to create templates of HTML inputs or you want to pass on the input markdown content unchanged.

Defaults: parseInput = true

suffix Optional

Replace the file extension part from input files and with the specified suffix when writing output files. For example, with suffix = "html" the input file "readme.md" will be output as "readme.html"

Defaults: no value and unused if unset

lua_dictionary Optional

Provide a custom and computationally enhanced dictionary (see the documentation for config key dictionary).

The lua file should return an anonymous function of the following format:

  function (dict) return newDict end

The returned dictionary will be merged with sajt's dictionary, and it will overwrite any keys present in sajt's dictionary.

Defaults: no value and unused if unset

lua_operators Optional

Register one or more custom sajt operators by providing the path to a lua file. The lua file should return/export a table, setting its keys to the names of the new operators. The value for each key should be an anonymous function of the format:

  local fn = function (input, [dict]) 
      -- local matched = input:match(PATTERN)
      return newString
  end

Defaults: no value and unused if unset

dictionary Optional

A way to specify freeform key-value pairs on a file-by-file basis. If you are familiar with "markdown frontmatter" then think of this like frontmatter that doesn't pollute the markdown file.

Example: We are setting dictionary for the group docs and its file content.md:

[docs.dictionary."content.md"]
key1 = "<value1>"
key2 = "<value2>"

Example: We are setting dictionary for all files processed by the config (a custom config-wide dictionary):

[general.dictionary]
globalKey1 = "<value1>"
anotherKey2 = "<value2>"

Just as with other overriding behaviour of sajt, the keys in the config-wide will be overridden if the same key is set for a group. Once that group has been processed, the config-wide dictionary will no longer be overridden.

Defaults: no value and unused if unset

[<groupName>] only

NOTE: [<groupName>]-only is applies if we have more categories than just general. If we have ONLY [general], then [general] will be able to use these keys if set!

outFilename Optional

Specifies the name of the output file.

Defaults: uses filename of the currently iterated file from files.

rename Optional

A table mapping input filenames to output filenames.

Example: The input file "./" will be saved as "index.html", and "config.html" will be saved as "config.html".

rename = { ./ = "index.html", config.html = "config.html"}

Defaults: uses filename of the currently iterated file from files.

replace Optional

A table mapping search strings to replacement strings. A search and replace is run for each file in the current section where replace is set (all files, if replace is set in [general]).

Example:

Each input file will have the string "github.com" replaced with "codeberg.org".

rename = { github%.com = "codeberg.org"}

NOTE: The search strings are Lua pattern matching expressions.

Notably, the following commonly used characters have to be escaped by putting a % in front:

The full set of characters that need to be escaped: ^$()%.[]*+-?

Defaults: If omitted, no replacements are attempted.

RSS specific keys - [<groupName>.rss]

Note: when we use optional and required here, we're referring to the whether the key is required for the RSS functionality -- not sajt at large.

RSS generation works by augmenting an existing section [<groupName>]. It has its own set of keys which are set in its own section, [<groupName>.rss].

RSS picks up and uses the following general keys set on the parent section [<groupName>]:

The rest of the keys in this section have to be set in [<groupName>.rss]

canonicalURL Required!

This is the base URL that will be used. It must be absolute and contain the protocol (http, https). It will be used in the <channel>'s <link> attribute, as well as the base for all RSS items.

For example, with canonicalURL = "https://docs-site.com" our feed would look like:

<rss version="2.0">
<channel>
    ...
    <link>https://docs-site.com</link>
    ...
    <item>
        ...
        <link><![CDATA[https://docs-site.com/content.html]]></link>
        ...
    </item>
</channel>
</rss>

title Required!

Sets the channel title property, which names the rss feed. The title is often prominently displayed in RSS readers.

relativeURL Optional

This sets the relative URL that will be used. If set it will be concatenated with canonicalURL as <canonicalURL>/<relativeURL> -- excess slashes are taken care of automatically.

If it isn't set, then all RSS entries will be relative to the root of canonicalURL.

description Optional

Sets the channel description property, which describes the rss feed as a whole.

Defaults: the empty string.

feedName Optional

Sets the name of the generated rss file.

Note: the feed file is saved relative to the outroot, which is often the base of directory that sajt's output is saved to.

Defaults: <groupName>.xml

On directories

Keys root, dirInput, dirFiles and non-[general] categories, [<groupName>], combine to derive the path for the input files passed through a given template.

Directories can be set as either relative to the current sajt project or as absolute paths.

Config examples

Rather than digest the entire set of config keys, it can be easier to understand how to use sajt by reading example config files. Let's go over a few of those.

Example

The config is at /var/www/beetle-site/sajt.toml. root, dirInput, and dirFiles are not specified. The config has only one category [beetles].

We would read the following input files, demonstrated below by using __ as separators for the different sajt-specific path segments. Since root, dirInput, dirFiles are not specified we show how they are evaluated to their relevant default values:

root__dirInput__dirFiles
->
/var/www/beetle-sajt__./__<groupName>
->
/var/www/beetle-sajt__./__beetles
->
/var/www/beetle-sajt/beetles    # this is where we try to read input files from

Another site might be structured differently, requiring using the key dirInput to read files.

Example

We have the following sajt.toml:

[general]
dirInput = "markdown-files"
template = "flour-info.html"

[durum]
files = ["index.md"]

[rye]
files = ["index.md"]

[rice]
files = ["index.md"]

The config is at /var/www/flour-site/sajt.toml. Key dirInput is set to the folder name markdown-files. Keys dirTemplate and dirFiles are not specified and left to their defaults. The config has the groups [durum], [rye], [rice]. Each group declares a file index.md.

Using this structure, the directory containing input files would be read as:

root__dirInput__dirFiles
->
/var/www/flour-site/__markdown-files__<groupName>
->
/var/www/flour-site/markdown-files/rye
/var/www/flour-site/markdown-files/durum
/var/www/flour-site/markdown-files/rice

Meaning input files for each group would be read as:

/var/www/flour-site/markdown-files/rye/index.md
/var/www/flour-site/markdown-files/durum/index.md
/var/www/flour-site/markdown-files/rice/index.md

Templates

Template variables

As sajt reads and applies a template to a set of content files, there are a set of template variables that can be used. Template variables have their value determined by the current input. Operate on these template variables using sajt's commands as you would any other variable.

CONTENT

Perhaps the most important template variable, CONTENT stores the verbatim input of the currently iterated content file.

${CONTENT} (using sajt's get command) inserts the content into the position of the template file where ${CONTENT} is written.

PAGE_NAME

The name of the content file stored in CONTENT as determined by its filename.

TEMPLATE_NAME

The name of the template file as determined by its filename.

GROUP_NAME

The config operates on groups, defined by the TOML syntax [group]; ${GROUP_NAME} retrieves the current group name.

ROOT_DIRECTORY

The directory all other directories are relative to; specified by config key root.

BASE_DIRECTORY

The directory storing the currently evaluated template file, as determined by dirname(template_file_fullpath).

CONTENT_DIRECTORY

The directory storing the currently evaluated content file, as determined by dirname(content_file_fullpath).

INPUT_DIRECTORY

The input directory for the current group; specified by config key dirInput.

TEMPLATES_DIRECTORY

The templates directory for the current group; specified by config key dirTemplates.

FILES_DIRECTORY

The files directory for the current group; specified by config key dirFiles.

FILES_LIST

A comma separated list of all the files being iterated on for the current group. Useful for constructing an index listing of multiple files for e.g. cross-linking or constructing a link index.

Advanced config example

Below you can find a rather complex example. It is making use of defaults provided by the [general] group, as well as per-group overrides.

Thanks to my friend glyph for making a thoroughly interesting site and putting the source code up for view.

[general]
root = "./"
outroot = "./site/"
dirInput = "templates/"
template = "templates/base.html"
extras = ["links/", "static/"]
files = ["index.html"]

[index]
dirFiles = "./"
dirOutput = "./"

[art]
[background]
[bicycles]
[japanese]
[lists]
[meditation]
[projects]
[support]

[bacteria]
files = ["index.html", "sauerkraut-beginnings.html", "sauerkraut-bottled.html"]

[computers]
files = ["index.html", "esp8266-dht11.html", "i2c-adventures.html", "rust-compilation.html"]

[fungi]
files = ["index.html", "design-patterns.html", "glossary.html", "grow-forests.html", "grow-together.html", "lichen-space.html", "mycomaterials_guide.html", "photo-guide.html", "reading-list.html"]

[plants]
files = ["index.html", "aloe-there.html", "blueberry-dance.html", "botanical-deceptions.html", "potato-tech.html"]

Directory listing after running sajt (all templated output resides in folder site/). Note how static/ and links/ have been copied to the output directory site/.

 site
 ├── art
 │   └── index.html
 ├── background
 │   └── index.html
 ├── bacteria
 │   ├── index.html
 │   ├── sauerkraut-beginnings.html
 │   └── sauerkraut-bottled.html
 ├── bicycles
 │   └── index.html
 ├── computers
 │   ├── esp8266-dht11.html
 │   ├── i2c-adventures.html
 │   ├── index.html
 │   └── rust-compilation.html
 ├── fungi
 │   ├── design-patterns.html
 │   ├── glossary.html
 │   ├── grow-forests.html
 │   ├── grow-together.html
 │   ├── index.html
 │   ├── lichen-space.html
 │   ├── mycomaterials_guide.html
 │   ├── network-resilience.html
 │   ├── photo-guide.html
 │   └── reading-list.html
 ├── index.html
 ├── japanese
 │   └── index.html
 ├── links
 │   └── style.css
 ├── lists
 │   └── index.html
 ├── meditation
 │   └── index.html
 ├── plants
 │   ├── aloe-there.html
 │   ├── blueberry-dance.html
 │   ├── botanical-deceptions.html
 │   ├── index.html
 │   └── potato-tech.html
 ├── projects
 │   └── index.html
 ├── static
 │   ├── art
 │   │   ├── aloe.jpg
 │   │   ├── archer.jpg
 │   │   ├── beetle.jpg
 │   ├── bacteria
 │   │   ├── sauerkraut_jar.jpeg
 │   │   └── sauerkraut_mountain.jpeg
 │   ├── bicycles
 │   │   └── bicycle.jpg
 │   ├── computers
 │   │   ├── debian_pi_pros_cons.png
 │   ├── favicon.png
 │   ├── feed.rss
 │   ├── fungi
 │   │   ├── earthstar.jpeg
 │   │   ├── earthstar_mycelium.jpeg
 │   │   ├── earthstar_roots.jpeg
 │   │   ├── photo_guide
 │   │   │   ├── bottom_view.jpg
 │   │   └── xanthoria_fun_dye.jpeg
 │   ├── glyph_laetiporus.jpg
 │   ├── glyph.svg
 │   ├── meditation
 │   │   └── fire_poi.jpg
 │   └── plants
 │       ├── potato_battery.jpeg
 │       ├── potato_sprout.jpeg
 │       └── tree_aloe.jpg
 └── support
     └── index.html