Quick Start

What is Skribilo?

Skribilo is a static site generator, it turns markdown files into html, which can then be hosted on the internet. Its primarily aimed at blogging, but the hope is it will be flexible enough for other purposes, e.g. documentation, this site is generated using Skribilo.

The project is heavily inspired by Makko and should be backwards compatible with version 2.1.2, but this is not guarenteed.

If its backwards compatible, why this?

Whilst I love Makko's simplicity, and I aim to keep it simple with Skribilo too, there are some features which I would like which Makko lacks:

  • handlebar partials
  • custom data per post
  • different layouts based on file structure
  • table of contents as data, not html

These features are not yet implemented in Skribilo either, but soon they will be.

Also this serves as a nice project to learn Rust.

Quick start

To get up and running with Skribilo, download the binary from the release page and place it somewhere on your path.

Open a terminal and change directory to where you wish to your site to live, then run the following:

skribilo .

On first run this will create all the required directories and files, and build a basic hello world blog.

If you with the site to be served locally run:

skribilo --live .

And you will be able to visit: http://localhost:8080 to see the site.

To view all available options run:

skribilo --help

The skribilo.json file

This file contains the all the main information about your new site, and will look like this:

{
	"title": "My Skribilo blog!",
	"description": "My new blog",
	"url": "https://www.example.com",
	"paths": {
		"symlinks_enabled": false,
		"cleanup_mode": "none",
		"source": "blog/",
		"templates": "templates/",
		"output": "web/"
	},
	"feeds": {
		"html": "index.html",
		"atom": "feed.atom",
		"rss": "feed.rss"
	},
	"custom": {},
	"callbacks": {
		"on_change": null,
		"on_create": null,
		"on_delete": null,
		"on_modify": null
	}
}

title, description, url

The title and description are the title and a desription of your new website, and url is the url where your site is hosted. These properties are used automatically in the ATOM and RSS feeds when building the site and in the default templates, they are all accessible in the templates on the website obejct, more on this later.

paths

symlinks_enabled

When creating your website you'll want some additional assets like images, css etc, Skribilo doesn't need to, and cannot, do any transformation on these files, by default it will copy these assets to the output directory if they don't already exist or have been modified.

However this creates 2 copies of the same file, this setting tells Skribilo to to create a link to the original file instead of copying it. For large assets this could save on disk space and time building the site.

This setting is not available on Windows systems, if it is set to true, it will be ignored.

source

Where are your markdown and asset files located?

output

Where do you want your built website to go?

templates

Where are the template files?

feeds

Skribilo supports 3 feeds, a HTML, RSS and ATOM feed. Replacing the values with null will tell Skribilo not to generate the fed.

html

Where would you like the output of the feed.html template? I would recommend leaving this as index.html so that the HTML feed appears as the home page for your site.

It is possible to change this to null and use a post as your home page by naming the post index.html, that is how this site works.

rss

Where would you like the RSS feed to be?

atom

Where would you like the ATOM feed to be?

custom

This is where customisation for the whole site can be configured, this property must be a JSON object but its properties can be any valid JSON.

For example this can contain keys for what posts should be displayed as links in a menu, what social media links to display, custom text to display on the HTML feed.

If a JSON object is piped into Skribilo when building the site the JSON will be merged with values in here e.g:

skribilo.json

{
  ...
  "custom": {
    "name": "Bob Bobson",
    "pets": ["dog"]
  }
}
echo '{ "pets": ["cat"], "likesPizza": true }' | skribilo .'

Would result in this getting passed to the templates when building the pages and the HTML feed:

{
  "name": "Bob Bobson",
  "pets": ["cat"],
  "likesPizza": true
}

Note: the array property is overwritten, the 2 arrays are not merged, the same happens with objects.

callbacks

These can contain commands which will be ran on certain events, the commands get details of the change piped into them on stdin.o

All callbacks will also be ran with a FROM_MAKKO, and FROM_SKRIBILO environment variable set to TRUE.

on_create

This runs anytime a new markdown file is created in the source directory.

The data piped into the command will look like:

[
  {
    "status": "created",
    "source": "/path/to/the/newly/created/file.md",
    "output": "/path/to/the/generated/html/file.md",
    "title": "the title of the post",
    "description": "the description of the post",
    "author": "the author of the post"
  },
  ...
]

on_delete

This runs anytime a file is deleted in the source directory.

The data piped into the command will look like:

[
  {
    "status": "deleted",
    "source": "/path/to/the/deleted/file.md",
  },
  ...
]

on_modify

This runs anytime a markdown file is modified in the source directory.

The data piped into the command will look like:

[
  {
    "status": "created",
    "source": "/path/to/the/newly/created/file.md",
    "output": "/path/to/the/generated/html/file.md",
    "title": "the title of the post",
    "description": "the description of the post",
    "author": "the author of the post"
  },
  ...
]

on_change

This runs anytime any of the above callbacks are triggered.

The data piped into the command will look like, an array of the same data which gets passed to

  • on_created
  • on_deleted
  • on_modify

Frontmatter

Frontmatter is a small bit of Yaml at the top of the markdown file, and will look like this:

---
title: My blog post
description: Here I talk about stuff
author: Me
visibility: public
tags:
  - Hello
  - World
created: 2026-05-18T14:33:05.000Z  
---
  • title Optional: this is the name of your blog post, and will be used in the feeds
  • description Optional: this is a brief description of you post, and will be used in the feeds
  • author Optional, defaults to OS username of unknown: this is the author of the post
  • visibility: public, secret, draft
  • tags Optional
  • created rfc3339 formatted date when the post was created

Visibility

Setting a post visibility to public will build it into the site, and add it to all feeds.

Setting a post visibility to secret will built it into a site, but will not add it to any feeds. This can be useful for creating pages.

Setting a posts visibility to draft will neither build it into the site, nor add it to any feeds.

Tags

These are a list of tags to help organise your blog, for backward compatibility it supports either a yml list, or a comma seperated string.

Posts

The body of posts must be valid markdown, you can find a guide to markdown here.

Templates

Templates use mustache, a logicless templating standard. There is a great manual here.

Why mustache when other static site generators like Hugo support more features in its templates? Two reasons:

  • its a standard with implementations in many languages, this makes the templates portable between different applications e.g Skribilo and Makko
  • its simple, the feature richness of hugo templates make them harder to understand and use

Skribilo currently supports 2 templates:

  • post.html: for generating HTML for all markdown files in the source directory
  • feed.html: for generating the HTML feed

In the future Skribilo may support a template file for each subdirectory in the source directory, defaulting back to the post.html file.

Currently Skribilo does not support partials in the templates, this is planned.

Template Data

When generating the HTML the templates get passed data which can be used inside the templates.

Post

Here is an example of the data which is passed to the post.html template:

{
  "post": {
    "is_secret": false,
    "title": "My Post",
    "description": "This is my post",
    "author": "Me",
    "tags": ["hello", "world"],
    "source": "blog/posts/hello-world.md",
    "url": "/posts/hello-world.html",
    "created": {
      "raw": "2026-05-20T15:33:26.000Z",
      "year": 2026,
      "month": "05",
      "day": "20",
      "hour": "15",
      "minute": "33",
      "second": "26"
    },
    "updated": {
      "raw": "2026-05-20T15:33:26.000Z",
      "year": 2026,
      "month": "05",
      "day": "20",
      "hour": "15",
      "minute": "33",
      "second": "26"
    },
    "body": "<h1>My post</h1>\n..."
  },
  "website": {
    "title": "My Skribilo site",
    "description": "A site where I blog stuff",
    "url": "https://www.example.com",
    "feeds": {
      "html": "index.html",
      "rss": "feed.rss",
      "atom": "feed.atom"
    }
  },
  "custom": {
    "my": "custom properties"
  }
}

If any of the optional data is missing from the frontmatter of a post, it will not be included in the data passed to the template.

The post.body contains the full HTML of the post.

Feed

Here is an example of the data which is passed to the feed.html template:

{
  "website": {
    "title": "My Skribilo site",
    "description": "A site where I blog stuff",
    "url": "https://www.example.com",
    "feeds": {
      "html": "index.html",
      "rss": "feed.rss",
      "atom": "feed.atom"
    }
  },
  "custom": {
    "my": "custom properties"
  },
  "posts": [
    {
      "is_secret": false,
      "title": "My Post",
      "description": "This is my post",
      "author": "Me",
      "tags": ["hello", "world"],
      "source": "blog/posts/hello-world.md",
      "url": "/posts/hello-world.html",
      "created": {
        "raw": "2026-05-20T15:33:26.000Z",
        "year": 2026,
        "month": "05",
        "day": "20",
        "hour": "15",
        "minute": "33",
        "second": "26"
      },
      "updated": {
        "raw": "2026-05-20T15:33:26.000Z",
        "year": 2026,
        "month": "05",
        "day": "20",
        "hour": "15",
        "minute": "33",
        "second": "26"
      },
      "body": "<h1>My post</h1>\n..."
    },
    ...
  ],
  "tags": [
    {
      "name": "hello",
      "posts": [
         {
          "is_secret": false,
          "title": "My Post",
          "description": "This is my post",
          "author": "Me",
          "tags": ["hello", "world"],
          "source": "blog/posts/hello-world.md",
          "url": "/posts/hello-world.html",
          "created": {
            "raw": "2026-05-20T15:33:26.000Z",
            "year": 2026,
            "month": "05",
            "day": "20",
            "hour": "15",
            "minute": "33",
            "second": "26"
          },
          "updated": {
            "raw": "2026-05-20T15:33:26.000Z",
            "year": 2026,
            "month": "05",
            "day": "20",
            "hour": "15",
            "minute": "33",
            "second": "26"
          },
          "body": "<h1>My post</h1>\n..."
        },
        ...
      ]
    },
    ...
  ],
  "by_tag": {
    "hello": [
      {
        "is_secret": false,
        "title": "My Post",
        "description": "This is my post",
        "author": "Me",
        "tags": ["hello", "world"],
        "source": "blog/posts/hello-world.md",
        "url": "/posts/hello-world.html",
        "created": {
          "raw": "2026-05-20T15:33:26.000Z",
          "year": 2026,
          "month": "05",
          "day": "20",
          "hour": "15",
          "minute": "33",
          "second": "26"
        },
        "updated": {
          "raw": "2026-05-20T15:33:26.000Z",
          "year": 2026,
          "month": "05",
          "day": "20",
          "hour": "15",
          "minute": "33",
          "second": "26"
        },
        "body": "<h1>My post</h1>\n..."
      },
      ...
    ]
  }
}

I'm not sure why there is both a tags, and by_tag property, both are only included for backwards compatibility.

Notable changes from Makko

  • the body of the post is passed into the feed.html template.
  • HTML posts via a .html-post extension are not supported.
  • running --live will always make the site accessible to other devices on the network, if you do not wish this to happen I would advise setting up the firewall on your device to prevent this.
  • no post ids, or hashes property in the skribilo.json file. Skribilo uses the modified date of the created posts to track what files need re-creating.