Minimal Python static site generator. Reads Markdown files, converts them to HTML with a template, and writes the output to a build directory. Zero dependencies beyond the Python standard library.
- Converts Markdown to HTML (headings, paragraphs, lists, code blocks, links, images, blockquotes, bold, italic)
- Parses simple front matter (
key: valuebetween---delimiters) - Generates an automatic index page listing all posts sorted by date
- Copies static assets from a
static/subdirectory - Serves the built site locally for preview
- Creates a starter site with example content
No pip packages required. Uses only the Python standard library.
git clone https://github.com/cappy-dev/markdown-to-static.git
cd markdown-to-static
chmod +x mds.pyOptional: add it to your PATH:
ln -s $(pwd)/mds.py ~/.local/bin/mdsInitialize a starter site:
python3 mds.py init my-siteBuild it:
python3 mds.py build my-site/content my-site/outputPreview it locally:
python3 mds.py serve my-site/outputThen open http://localhost:8000 in your browser.
Create a starter site with example markdown files:
python3 mds.py init my-siteThis creates my-site/content/ with three example files: index.md, hello.md, and about.md.
Build the static site from markdown content:
python3 mds.py build <content_dir> <output_dir>content_dir: Directory containing your.mdfilesoutput_dir: Directory where HTML files get written (created if missing, cleaned if it exists)
Options:
--template FILE: Path to a custom HTML template file--index-template FILE: Path to a custom index page template file--base-url URL: Base URL for the site (used in meta tags)
Serve the built site locally:
python3 mds.py serve <directory> [--port 8000]Starts a simple HTTP server. Press Ctrl+C to stop.
Each markdown file can include front matter at the top:
---
title: My Page Title
date: 2026-06-27
slug: custom-url
lang: en
---
Page content here.
Supported fields:
title: Page title. Falls back to the filename with dashes replaced by spaces.date: Publication date. Used for sorting on the index page.slug: URL-friendly filename for the output HTML. Falls back to a slugified version of the title.lang: HTML lang attribute. Defaults to "en".
The built-in parser handles:
- Headings:
# h1through###### h6 - Paragraphs: consecutive lines of text
- Bold:
**text**or__text__ - Italic:
*text*or_text_ - Links:
[text](url) - Images:
 - Inline code:
`code` - Fenced code blocks with language class: ` ```python ``
- Unordered lists:
- itemor* item - Ordered lists:
1. item - Blockquotes:
> quote - Horizontal rules:
---
Not supported: tables, footnotes, definition lists, task lists. If you need those, write raw HTML in your markdown.
Example content directory:
content/
index.md # optional site index page
hello.md # a blog post
about.md # another page
static/
style.css # copied as-is to output/static/
The builder outputs:
output/
index.html # auto-generated index (if no index.md exists)
hello-world.html
about.html
static/
style.css
Create an HTML file with {{title}}, {{content}}, and {{lang}} placeholders:
<!DOCTYPE html>
<html lang="{{lang}}">
<head>
<title>{{title}}</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<article>{{content}}</article>
</body>
</html>Build with:
python3 mds.py build content output --template my-template.htmlSame idea, but the index template uses {{post_list}} instead of {{content}}:
<ul>
{{post_list}}
</ul>Build with:
python3 mds.py build content output --index-template my-index-template.htmlRun the build on file changes with a simple loop:
while true; do
python3 mds.py build content output
sleep 5
doneOr add it to a CI pipeline that builds and deploys on every push.
Most static site generators need Node.js, Ruby, or a long list of Python packages. Sometimes you just want a script that reads some markdown and writes some HTML. That is all this is.
MIT