Readme
mdssg
A static site generator for a folder of linked markdown notes. One file, no dependencies, Linux and Windows.
python tools/build_pyz.py # produces one small mdssg.pyz
python mdssg.pyz init my-site
python mdssg.pyz serve -C my-site
Requires Python 3.11 or newer. That is the whole list.
This project is finished, and will rarely be updated. It does what I built it to do, and I would rather it stay small than grow. Do not wait on me for a fix or a feature — fork it and change it. Rename it, strip it down, take the parser or the graph code and build something else out of it, ship it commercially. The MIT licence already allows all of that and I am saying it out loud so nobody has to wonder. Issues and pull requests may sit unanswered; that is not rudeness, it is the stated deal.
Why another one
The generators that understand [[wikilinks]] are Node.js runtimes with a
heavy install. The ones that are small treat each markdown file as an isolated
document with no graph at all. mdssg takes the empty quadrant: graph-aware,
but tiny and dependency-free.
It builds the graph, not just the pages.
Wikilinks resolve by name. [[Kant]] finds the note wherever it lives.
Folders organise your writing and never appear in a URL, so moving a file breaks
nothing.
A link to an unwritten note is not a dead link. It becomes a seed page: a real page at a real URL, assembled from every sentence on the site that mentioned that name. The site is closed under linking — an internal dead link cannot be produced. And because seed URLs and note URLs come from the same name, writing the note later lands on the URL the seed already had.
Backlinks quote the sentence they came from, not just the title.
Related notes come from the shape of the graph — bibliographic coupling and
co-citation over your own links. No tags to maintain, no model to run, and
mdssg doctor --explain-related X will tell you why.
The graph is visible. Every build includes a compact, clickable /graph/
map of the written notes, seed pages and the links between them. It is an SVG,
not a JavaScript application. Large sites keep the drawing bounded to 180
nodes and 2,400 edges while the grouped text directory still lists every page.
Search stays static. /search/ loads a small JSON index generated with the
site and searches it with about 2 KB of dependency-free JavaScript. Nothing is
sent to a service. Each note also carries a compact graph of its immediate
neighbours.
Documentation
The documentation is the example site, and it is written with mdssg.
Read it as markdown right here in example/content/, or
build it and read it as a site:
python -m mdssg build -C example
python -m mdssg serve -C example
- Getting Started
- Wikilinks
- Seed Pages
- Backlinks
- Related Notes
- Markdown — what is supported, and what is deliberately not
- Configuration
- Templates
- Deploying and CGI
- How It Works — the design, and what turned out to be hard
Commands
mdssg init [dir] scaffold a site
mdssg build render into public/
mdssg build --clean discard previous output and cache first
mdssg build --index-only precompute the graph for CGI, render nothing
mdssg serve preview, rebuilding on change
mdssg new "A Title" [--post] create a note
mdssg doctor seeds, likely typos, orphans
mdssg doctor --explain-related X
What is in the box
Its own markdown parser, its own template engine, global and local link graphs, static full-text search, a themed 404, an incremental build, a dev server with live reload, RSS, canonical URLs, a sitemap, and a CGI handler for hosting that predates all of this. The dependency list is empty.
No plugin system, no shortcodes, no taxonomy engine, no asset pipeline.
Development
python -m unittest discover -s tests -t . -v
python tools/build_pyz.py && python tools/check_pyz.py
python tools/benchmark.py --notes 1500 --max-seconds 20
CI runs the suite on Linux, macOS and Windows across Python 3.11 through 3.14,
and runs it again against the packaged .pyz — reading the bundled theme
works in a checkout and fails inside a zip archive unless it is done properly,
so that last job is not ceremony.
Two tests carry more weight than the rest:
- closed under linking — after any build, every internal
hrefin the output resolves to a file that exists in the output - clean equals incremental — a clean build and an incremental build, after edits of every shape, must produce byte-identical trees
- large-site budget — CI builds 1,500 linked notes from scratch in at most 20 seconds and then requires an unchanged rebuild to write zero files
Status
Finished, and rarely updated. See the note at the top: fork it rather than waiting on me.
If you do build something on top of it, I would genuinely like to see it, but you owe me nothing beyond keeping the copyright line the licence asks for.
Licence
MIT. Copyright © 2026 Pablo Murad <pablomurad@pm.me>
You are free to use, modify, redistribute and sell this, with or without changes, for any purpose, without asking.