Usage
If webolator isn't installed yet, see the installation instructions.
webolator [OPTIONS] <INPUT>
Arguments:
<INPUT> Markdown file or directory
Options:
-o, --out <OUT> Output directory (default ./site), or output file with --single (default <name>.html)
--single Produce one self-contained HTML file
--serve Serve locally with live reload instead of writing output
--port <PORT> [default: 8000]
--mermaid-js <MERMAID_JS> Use this local mermaid.min.js (inlined/copied) instead of the CDN
--css <CSS> Extra stylesheet applied after the built-in styles (in addition to <root>/webolator.css)
--exclude <GLOB> Leave out files matching this gitignore-style pattern (repeatable)
--no-ignore Include files that .gitignore / .ignore would leave out (.webolatorignore still applies)
--no-git Don't add "last updated" dates and "edit this page" links from git
--check Fail (exit code 1) if there are broken or excluded links
--files <DIR> Show this folder as a file list instead of rendering its contents (repeatable)
--gallery <DIR> Show this folder as a thumbnail gallery of its images (repeatable)
--allow-large Publish files larger than 10 MB instead of failing
-h, --help Print help
-V, --version Print version
A folder
Every markdown file becomes a page. Each folder gets an index.html:
| Source | Result |
|---|---|
index.md or README.md in a folder |
that folder's index.html |
| a folder with no index | a generated listing page |
foo.md |
foo.html (or foo.md.html if a raw foo.html already exists) |
| any other file | copied as is |
hidden files and folders, node_modules, target, venv |
skipped |
| anything matched by an ignore rule | skipped, see Leaving files out |
Image galleries
--gallery works like --files, but shows the folder's images as a grid of thumbnails:
- Click a thumbnail to open the full image. Arrow keys step through the folder and Esc closes it.
- Thumbnails are generated (up to 320 px, PNG, JPEG, GIF and WebP) and written to a
_thumbsfolder next to the images. Images that can't be decoded, or that are already smaller than their thumbnail, are shown as they are. SVG and AVIF files are always shown as they are. - Other files in the folder (and subfolders) are still listed in a table below the grid.
- Everything else behaves like
--files: nothing inside is rendered, names are kept exactly, and it works with--single, where thumbnails and full images are embedded.
Folders of files
Some folders hold downloads, attachments or assets rather than documentation. Pass --files to list such a folder's contents instead of rendering them:
- Nothing inside is rendered. Markdown files are copied as
.md, HTML files are served as is, and everything else is copied. - The folder and every folder inside it get a file list with each file's name, size and last-changed date. The date comes from git when available, otherwise from the file's modification time. Each subfolder's list has a
../link back up. - The sidebar shows the folder once, tagged files and linking to the list, rather than every file in it.
- Links from your pages into the folder (
[installer](assets/setup.zip), or[assets](assets/)for the list) work as usual. - File names are kept exactly, including
01-prefixes, which only order the sidebar outside--filesfolders. - If the folder has its own
index.html, that file is kept and the list is written to_files.htmlinstead. - With
--single, every file is embedded, so downloads work offline. The output grows by the total size of the files.
The path is relative to the input folder, or to the current directory if it doesn't exist there. --files . turns the whole input into a file list. --files also works with a single-file input: the folder is included in full, whether or not anything links to it.
Leaving files out
Three kinds of rules keep files out of the site. All of them use gitignore syntax, including ** and ! negation.
-
.gitignorefiles are honored by default. This includes nested ones and those in parent folders, and it works even when the folder isn't a git repository. Build output and scratch files usually stay out without any setup..ignorefiles work the same way. Pass--no-ignoreto turn this off. -
.webolatorignoreis for files that belong in git but not on the site. It works like a.gitignoreand can be placed in any folder:drafts/ internal-*.md !internal-overview.md--no-ignoredoesn't affect it. -
--exclude <GLOB>works for one-off runs. It can be repeated, and patterns are relative to the input folder:
A link to an excluded file stays unchanged and is reported, so you'll notice if something you excluded is still linked:
warning: README.md: drafts/plan.md: target is excluded from the site
With --serve, excluded files aren't watched, and editing .webolatorignore or .gitignore rebuilds the site.
When the input is a single file, the site contains only that file and what it links to, so the ignore rules aren't needed there and aren't applied.
Large files
A build stops with an error if any file it would publish is larger than 10 MB, so a stray video or database dump doesn't end up on the site by accident:
error: 1 file over 10.0 MB would be published:
media/demo.mp4 (48.2 MB)
Leave it out with --exclude '/media/'
(or in .webolatorignore), or pass --allow-large
The suggested --exclude leaves out the whole folder holding each large file (or just the file, if it's at the top level). Use it as is, put a narrower pattern in .webolatorignore, or pass --allow-large if it really belongs on the site. --serve doesn't check, since it only previews locally.
Ordering
The sidebar is sorted by name, with files before folders. To choose the order, give files and folders a number prefix such as 01-usage.md or 02-guides/. Numbered items come first, in numeric order. The prefix doesn't appear in labels, and it's removed from output paths too, so 02-guides/01-setup.md becomes guides/setup.html. Links in your markdown still use the real file names.
A page can also set its position with order: in front matter, which takes precedence over the prefix. The same order is used for the previous and next links at the bottom of each page.
Checking links in CI
--check builds as usual, but exits with code 1 if there were any broken links, links to excluded files or unknown [[wikilinks]]. Use it to keep docs from rotting.
The site title is the first heading of the root README.md or index.md. If there is neither, the folder name is used.
A single file
webolator follows the file's local links, including links into parent folders, and builds a site from everything it reaches. The file you passed becomes the home page.
Preview
This serves the site on http://127.0.0.1:8000/. The site is rebuilt whenever a file changes, and open pages reload themselves.
Single HTML file
See Single-file output.
Links
You write links the same way you would for GitHub, and webolator rewrites them to point at the generated pages.
| You write | Static site gets | Single file gets |
|---|---|---|
[x](other.md) |
other.html |
#p-other |
[x](guide/) |
guide/index.html |
the guide's index section |
[x](../README.md#install) |
../index.html#install |
#p-readme--install |
[x](/guide/setup.md) (root-relative) |
the relative path to guide/setup.html |
#p-guide-setup |
[x](#section) |
unchanged | prefixed with the page id |
[x](report.html), [x](build.log) |
copied and linked as is | shown inside the page |
[x](spec.pdf) |
copied and linked as is | opened from embedded data |
https://…, mailto:… |
unchanged | unchanged |
[[Page name]] |
the matching page | the matching section |
Links written as raw HTML inside markdown, such as <img src="…"> and <a href="…">, are rewritten too.
See Wikilinks for how [[…]] names are matched.
Heading anchors
Heading ids follow GitHub's rules, so a #some-heading anchor that works on GitHub also works here. Hover over a heading to see its # link.
Broken links
A link to a file that doesn't exist is left alone and reported during the build:
warning: README.md: broken link: nope.md
Add --check to make these warnings fail the build.
Folders that work from disk
Links to a folder always point at its index.html explicitly. That way a static build also works when opened directly from disk (file://), without a web server.
Mermaid diagrams
A fenced code block whose language is mermaid becomes a diagram:
sequenceDiagram
You->>webolator: docs/
webolator->>Browser: index.html
Diagrams follow the page's light or dark mode.
Another example
stateDiagram-v2
[*] --> Scan
Scan --> Render
Render --> Write
Write --> [*]
Write --> Scan: file changed (--serve)
Offline use
By default mermaid.js is loaded from the jsDelivr CDN, and only on pages that contain a diagram. To avoid the network entirely, pass a local copy:
In a static build the file is copied to _webolator/. With --single it is inlined into the output file.
Syntax highlighting
Code blocks are highlighted at build time with syntect, so no JavaScript is involved and highlighting also works in single-file output. Colors follow the reader's light or dark mode.
"""First heading of every markdown file."""
return
for; do
done
Use the language's name or file extension after the fence (rust, py, sh and so on). A block with no language, or a language syntect doesn't know, is shown as plain text. The Syntax reference lists every supported language.
Single-file output
This produces one .html file that you can email, attach to a ticket or drop anywhere. This documentation as a single file is an example.
What's inside
- Every page becomes a
<section>, and only one is shown at a time. - Links become
#page-idor#page-id--heading, so the back and forward buttons and deep links work. - Images are inlined.
- Raw
.htmlfiles are shown in a frame, with a link to open them on their own. - Text files (
.txt,.log, source code) are shown as preformatted text. - PDFs and other files are embedded and open in a new tab when clicked.
- Mermaid diagrams are drawn when their section is first shown.
Size
Everything is embedded, so large PDFs or images make the file large. mermaid.js is loaded from the CDN unless you pass --mermaid-js.
Custom styling
There are two ways to add your own CSS. Both are applied after the built-in styles, so your rules win.
webolator.cssin the root of your docs folder is picked up automatically, so a styled site still needs no flags. It isn't listed as a page.--css path/to/file.cssadds a stylesheet from anywhere. If both exist,webolator.csscomes first and--cssafter it.
In a static build the combined CSS is written to webolator.css at the site root, so relative url(...) references work just as they do in your source folder. With --single it is inlined into the file, where relative url(...) references won't resolve, so use absolute URLs or data: URIs there. --serve reloads the page when either file changes.
Colors
All colors are CSS variables. Overriding them is usually all a theme needs:
}
| Variable | Used for |
|---|---|
--bg, --fg |
page background and text |
--muted |
secondary text: blockquotes, footnotes, tags |
--border |
rules, table borders, heading underlines |
--code-bg |
code blocks and inline code |
--link |
links and the active sidebar item |
--side, --hover, --active |
sidebar background, hover and current page |
--note, --tip, --important, --warning, --caution |
> [!NOTE]-style alerts |
A plain :root rule of yours overrides both themes. To change only the dark theme, target both ways it can be active: chosen by the OS setting, or by the theme toggle (which sets data-theme on <html>):
{ } }
}
Page structure
<!-- sidebar; missing when there's only one page -->
Site title
Page
folder…
… your markdown … … …
… <!-- only with 2+ headings -->
Inside article you get plain HTML (h1–h6, p, table, blockquote, pre > code), plus a few classes:
| Selector | What it is |
|---|---|
pre.mermaid |
a mermaid diagram |
pre.syntax-highlighting, .hl-* |
highlighted code and its tokens (.hl-keyword, .hl-comment, …) |
.markdown-alert, .markdown-alert-note, … |
GitHub-style alerts |
.footnotes |
the footnote list |
.anchor |
the # link shown when you hover over a heading |
.tag |
the small file-type label next to non-markdown files in the sidebar |
ul.listing |
the file list on a generated folder page |
.page-wrap |
the article plus its table of contents |
aside.toc, .toc-h2, .toc-h3 |
the "On this page" sidebar |
.page-meta |
the "Last updated · Edit this page" line |
nav.pager, .prev, .next |
previous / next links |
.code-wrap, button.copy |
code block wrapper and its copy button |
.theme-toggle, .menu-toggle |
the light/dark button and the phone menu button |
.math-inline, .math-display |
rendered math |
section.page |
one page, in --single output only |
Examples
A narrower serif reading column:
}
Hide the sidebar:
}
Writing
Everything from GitHub-flavored markdown works: tables, task lists, footnotes, strikethrough, autolinks and > [!NOTE] alerts. webolator adds a few extras on top. The Syntax reference lists every form with an example.
Math
Use $…$ for inline math, and $$…$$ or a ```math block for display math. It's rendered with KaTeX, which is loaded from a CDN only on pages that contain math.
Euler: e^{i\pi} + 1 = 0
\int_0^1 x^2\,dx = \frac{1}{3}
Emoji
Shortcodes like :rocket: and :tada: become emoji: 🚀 🎉
Wikilinks
[[Page name]] links to a page by name, as in Obsidian and similar note tools:
| You write | Links to |
|---|---|
[[setup]], [[Setup Guide]] |
the page whose file name or title matches |
[[guides/advanced]] |
a page by path, without the .md |
[[setup|how to install]] |
the same page, with custom link text |
[[setup#Step two]] |
a heading on that page |
[[#Step two]] |
a heading on the current page |
Matching ignores case and treats spaces, - and _ the same. A name that matches nothing is reported as a warning.
Front matter
An optional --- block at the top of a file adjusts that one page. Every field is optional:
| Field | Effect |
|---|---|
title |
replaces the first # heading as the page's title in the sidebar, browser tab and previous/next links |
order |
sets the sidebar position and takes precedence over a 01- filename prefix. On a folder's README.md or index.md, it positions the whole folder. |
hidden |
the page is still built and can be linked to, but it's left out of the sidebar and previous/next links |
The front matter itself is not shown on the page.
Reading
Every page gets these without any setup.
On this page
A right-hand sidebar lists the page's ## and ### headings and highlights the one you're reading. It appears on pages with at least two such headings, and it's hidden on narrow screens.
Previous and next
At the bottom of each page are links to the previous and next page, in sidebar order. That means the ordering you set with 01- prefixes or front matter also sets the reading order. Pages marked hidden: true are skipped.
Last updated and "Edit this page"
When the docs are in a git repository, each page shows the date of the last commit that changed it. When the origin remote is on GitHub or GitLab, it also shows a link to edit the file there. Files not yet committed get neither. Pass --no-git to leave both out.
In CI, fetch the full history (fetch-depth: 0 in actions/checkout). Otherwise every page shows the date of the latest commit.
Light and dark
Pages follow the operating system's setting. The ☾/☀ button in the sidebar switches themes, and the choice is remembered in the browser. Mermaid diagrams and code highlighting switch too.
Code blocks
Hover over a code block to show a Copy button.
Images
Click an image to see it full size. Click again or press Esc to close it. Images that are links behave as links.
Phones
On narrow screens the sidebar collapses into a ☰ menu at the top.
Publishing to GitHub Pages
This site is published by the following workflow (.github/workflows/pages.yml). To use the same approach in your own repository:
- In Settings → Pages, set Source to GitHub Actions.
- Add this workflow:
name: Docs
on:
push:
branches:
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # full history, for per-page "last updated" dates
- uses: astral-sh/setup-uv@v6
- run: uvx webolator docs -o _site --check
- uses: actions/upload-pages-artifact@v4
with:
path: _site
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v4
--check fails the deployment if a link is broken, so a bad link never reaches the published site. webolator's own workflow builds webolator from source instead of running uvx, so the docs always match the current code.
webolator
Point it at a folder of markdown and get a website. No config file, no index file, no SUMMARY.md.
This site is itself built by webolator from the docs/ folder of the repository. See Publishing to GitHub Pages for how.
How it works
flowchart LR
A[folder or .md file] --> B[scan / follow links]
B --> C[render markdown<br/>+ highlight code]
C --> D[rewrite links]
D --> E1[static site]
D --> E2[single HTML file]
D --> E3[live preview]
Features
- Zero config. The sidebar comes from the directory tree, and page titles come from each file's first
# heading. - Mermaid diagrams are rendered in the browser.
- Syntax highlighting happens at build time, with light and dark themes.
- Cross-file links to
.mdfiles, folders and headings are rewritten to the right place. - Sibling files (raw HTML, text, PDFs, images) are kept and linked.
- Single-file output puts a whole folder of docs into one
.htmlfile. - Live reload with
--serve. - File lists:
--files assets/lists a folder of downloads instead of rendering it. - Image galleries:
--gallery screenshots/shows a folder of images as a thumbnail grid. - Syntax reference: every supported markdown form, with examples.
- Reading comfort: an "On this page" sidebar, previous/next links, "last updated" and "Edit this page" from git, a light/dark toggle, copy buttons, image zoom and a phone menu.
- Writing extras: math, emoji,
[[wikilinks]]and optional front matter. - Leaves out junk:
.gitignoreis honored, and.webolatorignoreand--excludecover the rest. - Custom CSS: drop a
webolator.cssinto the folder, or pass--css.
See it in action
- The demo site is built from
examples/demo. It shows raw HTML, log and PDF files next to the markdown. - This documentation as a single file is every page of these docs in one self-contained HTML file.
Install
For a one-off run, you don't need to install anything:
To keep it around:
For every install option (uv, pre-built binaries, or building from source), see the installation instructions in the README.
Next: Usage.
Syntax reference
Every markdown form webolator understands. Standard markdown gets a quick summary, and the special forms are shown with their source and how they render on this page.
Standard markdown
Everything in GitHub-flavored markdown works as expected: headings, bold, italic, strikethrough, inline code, lists, - [x] task lists, tables (with :---: alignment), block quotes, footnotes1, images, horizontal rules, and autolinks for bare https://… and www.… URLs.
Raw HTML such as <details>, <kbd>, <sup> and <img width=…> is passed through as is, and its href and src attributes are rewritten like markdown links.
A few details specific to webolator:
- The first
#heading is the page title. - Every heading gets a GitHub-style anchor id, so
## Step twobecomes#step-two. ##and###headings fill the "On this page" sidebar.- Clicking an image zooms it.
- Images are inlined in
--singleoutput.
Emoji
Shortcodes use GitHub's names: :rocket: :tada: :+1: becomes 🚀 🎉 👍. A shortcode that isn't recognized is left as typed.
Links
| Form | Source |
|---|---|
| Link to another page | [Usage](01-usage.md) |
| Link to a heading | [ordering](01-usage.md#ordering), or [text](#links) on the same page |
| Link to a folder | [features](03-features/) goes to that folder's index page |
| Root-relative link | [x](/01-usage.md), relative to the docs root |
| Wikilink | [[Usage]], [[03-features/01-mermaid]], [[Usage|custom text]], [[Usage#Ordering]], [[#Links]] |
| Any other file | [log](build.log), [spec](spec.pdf), [report](report.html) |
For example, [[Usage#Ordering]] renders as Usage#Ordering. See Links for how each form is rewritten and Wikilinks for how names are matched.
Alerts
GitHub-style callouts, in five types:
Note
> [!NOTE]: useful information.
Tip
> [!TIP]: a helpful suggestion.
Important
> [!IMPORTANT]: something the reader must know.
Warning
> [!WARNING]: needs attention.
Caution
> [!CAUTION]: a risk of something going wrong.
Code blocks
The word after the fence can be a language name or a file extension. It's case-insensitive, so python, py, Rust and rs all work. These languages are highlighted:
ActionScript, AppleScript, ASP, Batch (bat, cmd), BibTeX, Bash (bash, sh, zsh), C, C#, C++, Clojure, CSS, D, Diff (diff, patch), Erlang, Go, Graphviz (dot), Groovy, Haskell, HTML, Java, Java properties, JavaScript (js), JSON, LaTeX (tex), Lisp, Lua, Makefile, Markdown, MATLAB, Objective-C, OCaml, Pascal, Perl, PHP, Python (py), R, reStructuredText, Ruby, Rust (rs), Scala, SQL, Tcl, Textile, XML, YAML (yml).
Anything else, including TypeScript, TOML, Dockerfile, Kotlin and Swift, is shown as plain text. Every code block gets a copy button.
Diagrams
A ```mermaid block is drawn with Mermaid: flowcharts, sequence, class, state and ER diagrams, Gantt charts and more.
flowchart LR A --> B
Math
| Form | Source |
|---|---|
| Inline | $e^{i\pi} + 1 = 0$ |
| Display | $$\sum_{n=1}^{\infty} \frac{1}{n^2} = \frac{\pi^2}{6}$$ |
| Display block | a ```math fenced block |
\sum_{n=1}^{\infty} \frac{1}{n^2} = \frac{\pi^2}{6}
\begin{aligned}
f(x) &= (x + 1)^2 \\
&= x^2 + 2x + 1
\end{aligned}
Math is rendered with KaTeX. As on GitHub, a $ followed by a digit doesn't close math, so "costs $5 and $10" stays plain text.
Front matter
An optional block at the very top of a file:
---
title: Shown in the sidebar and browser tab
order: 3
hidden: true
---
These are the only fields, and any others are ignored. See Front matter. This page uses it (title and order: 3) to sit between Links and the features folder.
Special file names
| Name | Meaning |
|---|---|
README.md, index.md |
the folder's home page (index.md wins if both exist) |
01-name.md, 02-folder/ |
number prefix: sets the order, hidden from labels and URLs |
webolator.css (in the root) |
custom styles, applied automatically |
.webolatorignore |
files to leave out, in gitignore syntax |
.gitignore, .ignore |
honored by default, --no-ignore turns them off |
To show a folder as a plain file list instead, see --files.
-
Like this one. ↩