Markdown#
mdssg has its own markdown parser, written from scratch, using nothing outside the standard library. This page is the specification: a behaviour is defined if and only if it is listed here and pinned by a test.
Supported#
Blocks — ATX headings (# ...) and setext headings (underlined with ===
or ---), paragraphs, thematic breaks, fenced code with a language, indented
code, blockquotes including nested ones, ordered and unordered lists nested to
any depth, task lists, tables with alignment, footnotes, and raw HTML blocks.
Inline — emphasis and strong, strikethrough, inline code, links, reference links, images, autolinks, hard breaks (two trailing spaces or a backslash), backslash escapes, raw HTML tags, and Wikilinks with aliases, headings and embeds.
Frontmatter — see below.
Tables#
| left | centre | right |
|:-----|:------:|------:|
| a | b | c |
| left | centre | right |
|---|---|---|
| a | b | c |
Wide tables scroll inside their own box rather than making the page scroll.
Task lists#
- [x] finished
- [ ] not yet
- finished
- not yet
Footnotes#
Some claim.[^why]
[^why]: The supporting detail.
Footnotes are numbered by the order they are referenced, not the order they are defined, and an unreferenced definition is dropped.
Deliberately not supported#
Each of these was left out on purpose, and the reason matters more than the omission.
Full CommonMark conformance. The spec has around 650 test cases, most of them describing interactions nobody writes on purpose. Chasing them would multiply the size of the parser to change the output of almost nothing.
Lazy continuation. In CommonMark, a paragraph following a blockquote is
absorbed into it without any > prefix. In mdssg a blockquote or list item ends
at the first line that does not carry its marker. This is the single most
surprising rule in the spec, and dropping it makes the block parser
substantially easier to reason about.
The seven HTML block types. A line starting with a known block-level tag
opens a raw passthrough that ends at the next blank line. Anything else starting
with < stays inline, so a paragraph can open with <em> without disappearing.
Named HTML entities beyond five. & < > " '
and numeric references pass through. Everything else spelled like an entity is
escaped. A partial entity table is worse than none, because it is impossible to
guess which half you got.
Two behaviours worth knowing#
snake_case_identifiers survive. Underscore emphasis uses CommonMark's
left/right-flanking rules, which is roughly twenty lines of the parser and the
difference between a tool that mangles variable names in prose and one that does
not.
__init__ does not. A name that both opens and closes at word boundaries
emphasises, exactly as CommonMark does with __foo__bar__baz__. Write it in
backticks.
Frontmatter#
YAML-shaped, but not YAML. It fails with a line number rather than guessing.
---
title: The Note's Name
date: 2026-07-18
aliases: [Another Name, A Third]
tags:
- one
- two
slug: an-explicit-url
draft: false
---
Supported: strings (quoted when they contain a colon), ISO dates and datetimes,
booleans, numbers, inline lists, and block lists. Comments must occupy a whole
line — a # inside a value is part of the value, because titles contain them
far more often than comments do.
Real YAML means anchors, flow mappings, multiple document styles and a type coercion table nobody remembers. That is a large surface for something whose job is holding a title and a date.
title sets both the note's name in the flat namespace and its
URL. slug overrides the URL only — and is itself slugified, so it cannot
contain a path separator or escape the output directory.
Images and video#
Images work normally, and get loading="lazy".

Video is by embed only. mdssg will not host, transcode or serve a video file; paste the provider's iframe into the note and it passes through as raw HTML. This is a scope decision, not a limitation to be fixed later.
Unsafe URLs#
javascript: and data: URLs are stripped from links and images, except
data:image/. This applies to what you write as well as anything generated,
because the parser cannot tell the difference between a note you wrote and one
you pasted.