Introduction
Mog is a markup syntax designed for rich, interconnected documents: knowledge bases, wikis, literate programs, and publishing on the web. It draws from Markdown, Org, and Neorg, but diverges with two-character delimiters, explicit nesting, and inline attributes. Together these create a unified grammar for expression . This document specifies the structure and rules for markup in a Mog document.
Table of Contents
1. Document Structure
1.1. Text Encoding
Documents are UTF-8 encoded files. A leading byte-order mark (U+FEFF) will be stripped by the parser and not considered document content. Line endings can be LF (U+000A) or CRLF (U+000D U+000A), parsing will normalize to LF.
1.2. Whitespace
The following Unicode characters are considered whitespace:
- Tab (0x09)
- Space (0x20).
Whitespace is not used to structure Mog. Leading and trailing whitespace will be trimmed when parsing a document. The following headings parse identically:
# My heading
# My heading 1.3. Paragraphs
A paragraph is a continuous sequence of text lines. Paragraphs are terminated by a blank line, structural marker, or the end of the document. Line breaks within a paragraph are parsed as soft wraps.
1.4. Metadata
Metadata may be optionally included at the beginning of a document, delimited by two hyphens. Data is formatted in KDL syntax:
-- title "My Document" authors "John" "Jane" date "2026-04-15" version 1 --
1.5. Escape Sequences
A leading backslash can be used to make any delimiter or marker literal. An isolated backslash does not need self-escaping because all delimiters are two characters.
\**this is not bold\**2. Structural Markers
Structural markers are used to give documents structure. These markers open the line
they are used on, ignoring leading whitespace. Structural markers are repeated to
explicitly indicate nesting. Using a heading # marker once is a
top-level heading, while ## two markers represents a subheading and so on.
These rules apply to all structural markers.
| Marker | AST Node | Semantic Meaning |
|---|---|---|
# | heading | Section heading |
- | unordered-list | Unordered list item |
. | ordered-list | Ordered list item |
> | blockquote | Blockquote |
= | thematic-break | Thematic Break |
2.1. Nesting
All structural markers can nest. Nesting is expressed through marker repetition. A repeated marker indicates a sub-entity:
# Heading 1 ## Heading 2 ### Heading 3 ## Heading 2 with leading whitespace # Heading 1 with leading whitespace
2.2. Blocks
Structured text may also span multiple lines. All structural blocks must have a matching set of closing markers.
- Reading notes -- Chapter 3 was particularly relevant: > ...quote from chapter 3... > --
2.3. Tasks
A task marker can be placed after any structural marker. Tasks are marked using
square brackets: [ ]. The status of a task is determined by the
character between brackets.
| Marker | Status |
|---|---|
[ ] | Undone |
[x] | Done |
[~] | In progress |
[?] | Uncertain |
[!] | Urgent |
[-] | Cancelled |
- [~] Grocery shopping -- [x] Eggs -- [~] Milk -- [?] Oat or almond? - [ ] Clean kitchen - [!] Call dentist - [-] Return sweater
3. Semantic Delimiters
Semantic delimiters are used both inline and as blocks. All delimiters open and close with two-characters. None can nest within themselves.
When semantic delimiters are parsed leading and trailing whitespace will be removed.
| Delimiter | AST Node | Semantic Meaning |
|---|---|---|
** | strong | Bold |
__ | italic | Italic |
`` | verbatim | Literal text |
~~ | strikethrough | Struck-through text |
$$ | math | Math expression |
|| | table-cell | Table cell |
-| | table-row | Table row |
[[ ]] | link | Link target |
(( )) | link-name | Link name |
{{ }} | link-footnote | Footnote content |
The quick brown fox jumped over the **lazy dog.** ** The quick brown fox jumped over the lazy dog. ** A paragraph **with soft wrapping** in it.
3.1. Math
Text contained within math delimiters is treated as verbatim, but evaluated as Typst math when rendered.
Einstein's famous equation $$E = m c^2$$ changed physics. $$ E^2 = (m c^2)^2 + (p c)^2 $$
3.2. Tables
Tables are declared in two parts: header and row. The header line of a table is
delimited using pipes ||. Following the header line, each entry row is
delimited by a hyphen and pipe -|. Table cells can include semantic
delimiters for presentation of tabulated data.
|| Name || Type || Color ||
-| Apple || Fruit || Red ||
-| **Carrot** || Vegetable || Orange ||
-| Blueberry || Fruit || Blue ||
|| Name || Type || Color || -| Banana || Fruit || Yellow || 3.3. Links
Links [[ ]] are formatted with square brackets. A link can be bare, or
it may have a name (( )) and footnote {{ }}.
- [[https://kdl.dev]] - [[https://kdl.dev]]((KDL)) - [[https://kdl.dev]]((KDL)){{ A node-based document language }}
Link resolution is driven by attributes. A bare link is a document reference and is
resolved against filenames and metadata title fields. Headings can be
referenced using the # attribute. External targets will be identified by
their protocol attribute.
[[recipe]] Document reference [[#:Ingredients]] Heading in current document [[https://kdl.dev]] External via protocol attribute
Footnotes
When footnote contents do not fit inline, they may be linked from elsewhere in the document:
A good marinara starts with San Marzano [[tomato]]((tomatoes)) and a generous amount of olive oil. [[tomato]]{{ A fruit not a vegetable }}
Footnotes can appear without a link. If unattached, a footnote will be indexed by its position in the document.
A good marinara starts with San Marzano
tomato{{ A fruit not a vegetable }} and a generous amount
of olive oil.4. Attributes
Attributes are generic data attached to a marker or delimiter, terminated using a colon. Attribute names may not contain whitespace. Following attribute declaration, an optional whitespace character may be inserted. If present, it will be trimmed when parsed.
##heading1:My Heading
Structural marker with no whitespace after attribute
##heading2: My Heading
Structural marker with whitespace after attribute
-list-attribute: list item with **red: BOLD** content Additiona attributes can be added as a chain, beginning from the terminating colon of the prior attribute. Attribute chains must stay on a singe line, newline or whitespace declares non attribute content.
##attribute1:attribute2: My Heading
For complex data attribtes may be structed as Janet forms. The following Janet forms are permitted:
-{:key "value"}: Table attribute
-["item 1" "item 2"]: List attribute Here you can see attributes added to a heading, list item, and bold delimiter. If
you are reading this document as text you may also see the above code blocks
language attribute: mog.
Chains
These attributes enable much functionality, but we often want more than one value. This is why you must use a closing colon:
##upcase:red: My Heading