#ssg #static #site #generator

app mksite

A file format-agnostic static site generator

3 releases

0.1.2 Oct 17, 2022
0.1.1 Oct 15, 2022
0.1.0 Oct 14, 2022

#395 in Command line utilities

MIT license

644 lines


A file format-agnostic static site generator


If you already have Rust and Cargo installed:

cargo install mksite

Alternatively, you can install via git:

cargo install --git https://github.com/alterae/mksite


mksite <COMMAND>

  build  Build the site according to `mksite.toml`
  clean  Delete all build outputs
  init   Initialize a `mksite.toml` file in the current directory
  new    Scaffold an empty site in a new directory
  help   Print this message or the help of the given subcommand(s)

  -q, --quiet                  Do not print log messages
      --log-level <LOG_LEVEL>  What level of logging to enable (error, warn, info, debug, or trace) [default: info]
  -h, --help     Print help information
  -V, --version  Print version information

mksite is a program for turning a tree of text files into a different tree of text files, usually a website. A typical mksite project has the following structure:

  • mksite.toml — The mksite.toml file is the core of a project, and defines the names of the other significant directories, the data available to templates, and the transforms to apply to files.
  • layout/ (optional) — The layout/ directory contains layouts, such as html boilerplate, to be applied to files after they are transformed. If you don't want any layouts, or don't want to use mksite's layout system, this folder can be safely omitted. The name of the layout/ directory can be customized in mksite.toml.
  • out/ (generated) — The out/ directory is generated by mksite when the site is built, and contains the transformed contents of the src/ directory, as well as the contents of the static/ directory, copied as-is. The name of the out/ directory can be customized in mksite.toml.
  • src/ — The src/ directory holds all the non-static source files for the website. Files in src must be valid UTF-8 and can contain template expressions using the Tera templating language. Other than that, they can be anything: LaTex, HTML, Markdown, your own custom markup language, or something else entirely. The name of the src/ directory can be customized in mksite.toml.
  • static/ (optional) — The static/ directory contains static files, such as stylesheets or images, which are copied as-is to the out/ directory. No templating or transformation occurs. The name of the static/ directory can be customized in mksite.toml.


An example mksite.toml file looks like this:

dirs.src = "src"
dirs.out = "out"
dirs.static = "static"
dirs.layout = "layout"

author = { name = "Jane Doe", email = "email@example.com" }
copyright = 2022

md.html = "pandoc -f markdown -t html"
scd.html = ["scdoc", "pandoc -f man -t html"]

The first four lines tell mksite the names of the src/, out/, static/, and layout/ directories, respectively. Changing these will change where mksite reads and writes data. For example, dirs.out = "www" would cause mksite to write the build output in a folder called www/.

Next is the data section, which is where you can define arbitrary data that will be passed to the template rendering. In templates, this data will be made available under the data variable. For details on the template syntax, see the Tera documentation.

Finally, we have the transforms section. Transforms are commands or chains of commands that take a stream of bytes on standard input, and return a stream of bytes for standard output. Transforms can be used to trivially implement many features mksite does not natively support, such as markdown rendering and syntax highlighting. The basic syntax of a transform definition is in.out = "command" or in.out = ["command1", "command2", ...] where in is the file extension the transform operates on, out is the file extension the transform produces, and command is a command to pipe the page through. For more details on the finer points of transforms, see below.

All fields in this config file are optional.


Layouts are simply Tera templates located in the layout/ directory that accept an additional page.content variable. Layouts must be valid UTF-8, but aside from that they can be any format.

An example layout file looks like this.

<!-- layout/_.html -->
<!DOCTYPE html>
    <!-- The "| safe" prevents Tera from html-escaping the content. -->
    {{ page.content | safe }}

There are two kinds of layouts: default layouts and override layouts:

Override layouts have the same name, extension, and relative path as a specific file in the out/ directory, and only apply to that file. For example, the layout layout/blog/index.html will only apply to the page out/blog/index.html.

Default layouts have the name _.ext, where ext is some file extension. Default layouts apply to all files in the corresponding directory and in nested directories that don't have a default layout. For example:


In this example, layout/blog/_.html will apply to all html files in out/ except index.html, and layout/_.html will apply to every html file in out/ except the contents of the blog directory.

Note Layouts are applied after transforms, based on the file extension of the transform output. As a result, a layout like _.html will apply to all generated html files, regardless of whether these html files were hand-written or generated from markdown or something else via a transform.

If no applicable layouts are found for a file, or if there is no layout/ no layout will be applied.

If an applicable layout exists, but you would like to prevent it from being applied to a file or folder, you can define an "empty" layout like so:

{{ page.content | safe }}


A transform has an input extension, an output extension, and a command or chain of commands.

Each page in src/ with a file extension matching a transform's input extension is piped into the transform as a stream of bytes, and the resulting stream of bytes is written to a file in the out/ directory with the same name and path, and a file extension matching the transform's output extension. Each page can be piped to multiple transforms, and multiple transforms can output the same format. The relationship between inputs and outputs is many-to-many.

Warning It is important to note that the inputs and outputs of transforms are streams of arbitrary bytes, not necessarily valid UTF-8 strings. This is important for interfacing with external non-rust tools, but there are some caveats:

Though this may change in the future, at present all templates and layouts must be valid UTF-8. This means that while transforms can both input and output arbitrary bytes, the original input to a transform (a file in the src/ directory) will be UTF-8. Additionally, layouts for non-UTF-8 files are not supported, and attempting to define a layout for, say, a .pdf file will result in an error.

If a transform has a single command, pages are piped to that command, and the output of the command is written to the output file. For example, these transforms:

md.html = "pandoc -f markdown"
md.pdf = "pandoc -f markdown -t pdf"

Will use pandoc to produce an html file and a pdf document in out/ for every markdown file in src/.

If a transform has a chain of commands, pages are piped to each command in the chain in order, and the ouput of the last command is written to the output file, like with shell pipelines. For example:

scd.html = ["scdoc", "pandoc -f man"]

Will use scdoc to generate a man page from each .scd file, and immediately pipe that man page to pandoc to convert it to html.


Pull requests and issues are welcome, but please ensure you run cargo fmt before submitting a PR.


See the docs/ folder for an example of a website built using mksite.


mksite is licensed under the MIT License.


~178K SLoC