Toaster is a static website generator that reads a structured folder of markdown files and produces a folder of HTML files, ready to be browsed. It does a bit more than that, but that’s the basic idea.
Each markdown file is a page in your website. Toaster converts each page into an HTML file, inserts a user-defined head into each page, and checks that all internal links are valid, printing a warning if a link is broken. Internal links are made relative to the current page, so the generated website can be opened and browsed offline.
To see how the website structure is supposed to work, see Website structure. To learn the non-standard markdown syntax used for writing website pages, see Markdown syntax.
To generate an HTML website using Toaster, run the following command:
toaster <source> <destination> --html
<source> is the path to your folder of markdown files, and <destination> is the path to a folder where the generated website will be placed. The --html flag instructs Toaster to generate HTML files for each page, otherwise only markdown and static files will be saved to the output folder.
The --delete flag will delete any existing output folder before generating the website. Toaster will normally leave existing output files untouched, and will only copy static files if they don’t already exist or if the source version is newer than the destination version. This makes Toaster much faster when generating websites that contain many gigabytes of static files.
The --verbose flag will enable detailed logging. A message will be printed for every file generated, deleted, copied, or linked.
The --version flag will print version information and exit. The --help flag will print usage information and exit.
The ‘last modified’ time of every file copied or generated by Toaster will be taken from the source file. This allows Toaster to save time by skipping files that haven’t changed since the previous run, and it also allows tools like rsync to ignore unmodified files when uploading to a web server.
The structure of the source folder determines the structure of the generated website. The name of the folder is the name of the generated website, which is used in the tab name for each page.
We’ll use the following folder structure to explain how Toaster works with files. You can download this website from example-website.zip so that you can build it and see how it works.
Each markdown file is a page in the generated website. The name of the file is the name of the generated page, which is used as the title at the top of the page and in the tab name.
The markdown syntax used by toaster is slightly different to standard markdown, making it simpler and easier to read. See the markdown syntax section to learn how to write a page using this syntax.
Each page is given an ID by converting all letters in the page name to lowercase and replacing all spaces and punctuation with hyphen characters. For example, the page Wholegrain bread will be given the ID wholegrain-bread. This ID is used as the page URL.
An internal link doesn’t have to match a page name exactly, the link name and the page name just have to resolve to the same ID. This means that the internal links {Wholegrain bread} and {wholegrain BREAD} will both point to the wholegrain-bread page.
If the filename for a page starts with a sequence of digits and hyphens, Toaster will remove that sequence from the page name and ID. This is useful for adding dates or numbers to file names so that they sort nicely in the file browser. For example, the page 2026-02-25 My cool trip.md will have the name My cool trip and the ID my-cool-trip.
A page with the name +index.md is the index page for the parent folder. For example, the file /Projects/+index.md will become the file /projects.html in the output.
Internal links on index pages are relative to that parent folder, so the internal link {Toaster} on the Projects/+index.md index page will link to the page /Projects/Toaster.md.
A small collection of variables can be inserted using curly braces, as follows:
{site_name} The name of your website, taken from the name of the source folder.
{page_name} The name of this page with markdown styling removed.
{page_name_html} The name of this page with markdown styling converted to HTML. Useful in conjunction with the override-titlecode blocks.
{home_link} A link to the home page of your website. The link text is the website name.
{parent_link} A link to the index page of the parent folder. The link text is the parent folder name. This will be an empty string on the root index page.
{head} The value of the html.head key in the configuration file. The embed-html-head and override-html-head code blocks will add to or override this value.
{toc} The table of contents for this page as a <ul>, with each heading in the page as a <li> containing a <a>. Each <li> has a class l1, l2, or l3 depending on the heading level, and the link text is the heading name.
{toc_compact} A <details> dropdown element containing the table of contents. If there are fewer than 3 headings on the page, this will be an empty string.
Any file that doesn’t have the extension .md, .feed, or .redirect is a static file. Static files are copied directly to the output folder without modifying the name or contents. For example, the file /Projects/unlock_key.txt will become the file /projects/unlock_key.txt in the output folder.
Prefixing a folder name with an exclamation mark will make that folder into a static folder, and every file inside will be treated as static. The folder will be copied directly to the root of the output folder. For example, the folder /Projects/!images will become the folder /images in the output folder.
If multiple static folders with the same name exist in the source folder, the contents of every static folder with that name will be combined into a single folder in the output. For the example website, the output folder contains a folder /images that contains the contents of all of the source !images folders. This is useful for keeping media files close to where they’re used in the source, but grouping them together in the output so that they can be served differently.
You can link to a static file from a page using an external link. For example, the external link </images/bread_resized.jpg> will link to the matching output file. Toaster will check all links to static files, printing a warning if a link is broken. To link to a website instead, make sure to prefix the URL with https://.
A file with the extension .redirect becomes an HTML file that redirects to another page. The redirect file contains an internal link to the target page. For example, the file Bread.redirect contains the line of text /Recipes/Wholegrain bread, so a user visiting the page /bread.html will be redirected to the page /recipes/wholegrain-bread.html instead. This is useful for creating short URLs for popular pages, or to move a page without breaking links on other websites.
To automatically create a root redirect for every page in a folder, use the hoisted_folders key in the configuration file. The value of this key should be a list of folder paths. For example, if the value is /Recipes, the redirects /Carrot cake.redirect and /Wholegrain bread.redirect will be created in the root folder while the website is being generated.
A file with the extension .feed will generate a matching RSS feed. The source file /Recipes/updates.feed will become the output file /recipes/updates.rss.
The first line of the file is the title of the feed, and every non-blank line after that will be an entry in the generated RSS feed. Each line is a timestamp, a :: separator, and an internal link to a page on the website. The timestamp uses the RFC3339 format, with the time T00:00:00 and local timezone appended automatically if omitted. The key rss.base_url in the configuration file must contain the URL that you will be using to host your website. The ‘last modified’ time of the .feed file will be used for the <lastBuildDate> value.
The following is an example .feed file from this website.
Weekly Debrief — Ben Bridle
2026-02-20::Making noise
2026-02-06::Assembling in style
2026-01-30::Plans for Bedrock
Toaster uses two types of link: internal links point to pages and headings inside this website, and external links point to static files and other URLs. Both types are checked by Toaster, a warning will be printed if a link points to a non-existent page, heading, or static file on your website.
Internal links are denoted with curly braces, and can point to a different page, a heading on a different page, or a heading on the current page.
{#RSS feeds} Points to the heading #rss-feeds on the current page. The link text will be RSS feeds (the heading name).
{/Projects/Toaster} Points to the page /projects/toaster.html. The link text will be Toaster (the page name).
{/Projects/Toaster#Usage} Points to the URL /projects/toaster.html#usage. The link text will be Usage (the heading name).
Links can be given as relative links by omitting the leading slash. The link {Toaster} on the page /Projects will link to the page /Projects/Toaster. The parent component ../ can also be used.
An optional separator :: can be used to add a custom label to a link. The link {project page::/Projects/Toaster} will point to /projects/toaster.html, but the link text will be project page.
External links are denoted with angle brackets, and can point to static files or external websites.
</static/example-website.zip> Points to the static file /static/example-website.zip in this website. The link text will be example-website.zip (the file name).
<assets/loader.js> Points to a static file assets/loader.js relative to the URL of the current page. The link text will be loader.js (the file name).
<https://benbridle.com/toaster> Points to an arbitrary URL if the link begins with https:// or http://. The link text will be benbridle.com/toaster (the URL without the scheme).
<mailto:ben@derelict.engineering> A mailto link, opens the user’s email client when clicked. The link text will be ben@derelict.engineering (the email address).
An optional separator :: can be used to add a custom label to a link. The link <contact me::mailto:ben@derelict.engineering> will have the link text contact me.
The file toaster.conf is used to set additional options for your website. The file contains keys and values.
Each line with no leading whitespace in the file is a key. The value of the key is all of the following lines that do have leading whitespace. Here’s a quick example of what this looks like:
first.key
This is a
really long
value
second.key
a short value
The following keys are supported:
html.head An HTML fragment to insert into the <head> element of every HTML website page. See HTML template.
html.redirect.head An HTML fragment to insert into the <head> element of every generated HTML redirect.
html.template An HTML document to use as the base structure for every HTML website page. See HTML template.
rss.base_url The base URL of the website, used when generating RSS files. See RSS feeds.
hoisted_folders A list of folder path. An implicit redirect will be created at the root of the website for each page in the folders. See Redirects.
The markdown syntax used by Toaster is slightly different to standard markdown, making it simpler and easier to read. To see the raw markdown for any page on this website, you can add the extension .md to the end of the page URL.
You can use special styling syntax inside every markdown element, making text bold, italic, monospace, and adding math equations or links. Styling syntax can’t be nested, so you won’t be able to have styled text inside a link or italic text inside bold text.
See links to learn how the link syntax works, and how to link to pages, headings, files, and other websites.
This line contains *bold* text, _italic_ text, `monospace` text, a $math$ equation, an {internal link::/Projects/Toaster}, and an <external link::https://benbridle.com>.
This becomes the following HTML:
<p>This line contains
<b>bold</b> text,
<i>italic</i> text,
<code>monospace</code> text,
a <span class='math'>math</span> equation,
an <a href='../../projects/toaster.html' class='page'>internal link</a>,
and an <a href='https://benbridle.com' class='external'>external link</a>.
</p>
The rendered elements will look like the following:
This line contains bold text, italic text, monospace text, a math equation, an internal link, and an external link.
Headings are created by prefixing a line with one, two, or three hash characters and a space. These map to the heading elements <h1>, <h2>, and <h3> in HTML.
Each heading in a page is given an ID by converting all letters to lowercase and replacing all spaces and punctuation with hyphen characters. For example, the heading # First-level heading will be given the ID first-level-heading. The HTML generated for each heading contains an <a> element that links to itself so that you can right-click a heading and ‘Copy link’ in a web browser to point to that exact heading.
If multiple headings in a page share the same ID, Toaster will attempt to make the duplicate headings unique by prefixing them with the ID of the most recent first-level heading. For example, if you have the first-level headings # Kinematics and # Circular motion in your page, each containing a subheading ## Notes, the IDs for those headings will be kinematics-notes and circular-motion-notes. When linking to these headings, you’ll be able to link with {#Notes} within the same first-level section, but you’ll need to give the full name {#Kinematics notes} everywhere else. Try to avoid having duplicate headings if you can.
The first unstyled colon character in a list element will be converted to a line break element <br> in HTML, and the list item element <li> will be given the class extended to help with styling. This is useful for making definition lists, where each list element is a short name followed by a short explanatory paragraph.
- First element: This is the first element of the list
- Second element: This is the second element of the list
- Third element: This is the third element of the list.
This becomes the following HTML:
<ul>
<li class="extended">First element<br>This is the first element of the list</li>
<li class="extended">Second element<br>This is the second element of the list</li>
<li class="extended">Third element<br>This is the third element of the list.</li>
</ul>
The rendered list will look like the following:
First element This is the first element of the list.
Second element This is the second element of the list.
Third element This is the third element of the list.
If a list item starts with [x] or [ ], a checked or unchecked checkbox will be inserted at the start of the list element and the list item element <li> will be given the class checkbox to help with styling. This is useful for showing items in a list that have or haven’t been completed. Checkboxes are given the disabled state to make it clear that they cannot be interacted with.
- [x] Hang up laundry
- [x] Wash dishes
- [ ] Vacuum the house
Math equations can be entered by surrounding a line with pairs of dollar-sign characters. You will need to perform the equation rendering on the client side using KaTeX or similar, this element is only provided to make it easy for your renderer to find your equations.
$$ \sum_{i=1}^n c = nc $$
This becomes the following HTML:
<div class='math'>\sum_{i=1}^n c = nc</div>
The equation will look like the following when rendered with \KaTeX:
\sum_{i=1}^n c = nc
Take a look at the render_math.js script on this website to see how client-side rendering with \KaTeX can be implemented.
Media files can be embedded into a page by prefixing a link to a static file with an exclamation mark. Depending on the file extension, this maps to the <img>, <video>, or <audio> elements in HTML. Toaster will check to make sure that the linked media file exists, printing a warning if the link is broken.
!<Logo over a desolate beach::/images/Derelict beach (banner).jpg>
This becomes the following HTML:
<figure>
<a href="../../images/Derelict beach (banner).jpg">
<img src="../../images/Derelict beach (banner).jpg" alt="Logo over a desolate beach" title="Logo over a desolate beach">
</a>
</figure>
The rendered image will look like the following:
If a description is supplied in the link (before the optional :: separator), that description will be used as both alt text and title text to make your website more accessible to people with impaired vision. The <img> element is wrapped with a link element <a> so that clicking on the image opens the image at full size in a new tab, and the whole thing is wrapped in the figure element <figure> to help with styling.
Supported image file extensions are .jpg, .jpeg, .png, .webp, .gif, and .tiff.
Audio and video files are also supported. Supported audio file extensions are .mp3, .wav, and .m4a, and supported video file extensions are .mp4 and .avi.
Column alignment is performed by the colon characters in the second line, dividing the headers from the body. Left-aligned columns have a colon on the left side, right-aligned have one on the right, and center-aligned have one at either end. If no colon is present, the column is left-aligned. Alignment is denoted by the classes l, r, and c, meaning left, right, and center.
If a cell only contains the text Yes or No, that text will be replaced with a ✓ or ✗ character, and the ✗ character will be given the class dim. If a cell only contains the text --, the cell will be given the class dim and the text will be centered.
Additional separator lines in the table body will split the body into multiple sections with </tbody><tbody>. A separator line can only contain the characters |, :, and -.
A double-thickness vertical border (like ||) will give all body cells to the left of that border the class border.
Code blocks are created by wrapping the block of code in triple backticks on the preceding and following lines, with the code language written after the first triple backtick. This maps to the pre-formatted text element <pre class="language">, with the language as the class.
There are some syntax names that will act differently on a page.
embed-html The contents will be inserted directly into the HTML of the generated page. This is useful for adding buttons, inputs, and other special elements to a page.
embed-css The contents will be inserted directly into the HTML of the generated page wrapped in <style> tags. This is useful for adding JavaScript code to a page for interactivity.
embed-js/embed-javascript The contents will be inserted directly into the HTML of the generated page wrapped in <script> tags. This is useful for adding additional styles to a page.
embed-html-head/override-html-head The contents will either be appended to or completely overwrite the contents of the <head> element on the page. This is useful for including additional resources on a particular page, or to replace the site-wide HTML head with custom code for a particular page.
override-title The contents will be used for the title of the page, displayed at the top of the page and as the tab name. This is useful for adding markdown styling syntax to a name, or to be able to use a short name for the URL and a more elaborate name for the display.
math The contents will be wrapped in a <div class="math"> element exactly like with the math equation markdown syntax. This allows you to write a multi-line equation on a page.
poem The contents will be wrapped in a <div class="poem"> element, with blank lines replaced with line break elements <br>.
recipe The contents will be parsed as a cooking recipe using a terse syntax, with ingredients declared inline. See recipe syntax below for details.
gallery/gallery-nav The contents are a list of file names for images stored in a root /images folder. Clicking an image in a regular gallery will open a large variant of the image in a new tab, and clicking an image in a navigation gallery will navigate to the linked page. See gallery syntax and gallery-nav syntax below for details.
hidden/todo This code block will not be rendered as anything. This is useful for adding invisible notes to a page or for temporarily hiding content.
For the recipe syntax, ingredients are declared inline using curly braces. A comma separates the quantity from the ingredient, and a second comma can be used to add a brief note. Toaster will parse out the ingredients into a separate list above the recipe.
```recipe
Cream {butter, 200 grams, softened} with {sugar, 1 cup}. Add {cinnamon, a pinch} to taste.
```
This becomes the following HTML:
<div class='recipe'>
<ul>
<li>200 grams of butter (softened)</li>
<li>1 cup of sugar</li>
<li>a pinch of cinnamon</li>
</ul>
<hr>
<p>Cream butter with sugar. Add cinnamon to taste.</p>
</div>
For the gallery syntax, each line is the file name of an image found in a root /images folder, or in the thumb and large sub-folders if they exist. The gallery will show the image from the /images/thumb folder to save bandwidth, and clicking an image will open the image from the /images/large folder in a new tab for full quality. If the image doesn’t exist in these folders, it will be loaded from the root /images folder instead. Toaster will check to make sure that each listed image exists, printing a warning if missing.
```gallery
Art day.png
Waitangi Park.png
Wires.png
```
The gallery-nav syntax works similarly to the gallery syntax, but clicking on an image will navigate to a website page instead. The non-optional :: separator is used to separate an internal link from the image file name. Toaster will check to make sure that each link is valid, printing a warning if not.
All other syntaxes can be syntax highlighted during the build step. The value given for the highlighters key in the configuration file will control how each language will be highlighted.
The highlighters value in the configuration file will contains the syntax definitions for every highlighted language on your website. This value is split into blocks by a language line wrapped in square brackets, and every line following is a rule for the syntax of that language. For example, look at the following syntax definition for a Lua or PICO-8 program:
This syntax will be applied to code blocks with the languages lua, pico8, and p8. Each rule in the definition is made up of a name, followed by an equals sign, followed by a regular expression. If the name is wrapped in angle brackets, like the <comment> rule above, then that expression will be inserted in place of the name <comment> in all following expressions (wrapped in a non-capturing group). Otherwise, all substrings matching the expression will be wrapped in a <span class="name"> element, where name is the name of that rule converted to lowercase (such as primary or dim).
function early_or_late(year)
if year < 2000thenreturn "early"elsereturn "late"endend
If the name of a styling rule is followed by additional comma-separated names in parentheses (like REG(GREEN, BLUE)), then each successive capture group in the regular expression will be styled with the matching name (the first capture group will have the class green, the second will have the class blue, and the characters that fall outside of any capture group will have the class red).
If you have an error in your syntax definition or regular expressions, Toaster will crash out with a panic error that mentions the package highlight. The error message won’t be very specific, sorry about that.