📝 MD_SYNTAX_MANUAL.md

Markdown BBCode Mod — Supported Syntax Reference

Generated from MDParser.php's actual implementation as of today's GFM-conformance pass. Every row below is something the parser genuinely handles, not aspirational.

Before posting any part of this document to the forum: SMF has no automatic escaping for text that merely looks like BBCode. A literal [md] typed or pasted into a normal post is a real tag-open, not example text — and since it's registered as unparsed_content, everything after it gets swallowed until a matching [/md] (or the rest of the post just disappears into the block). Backticks/code formatting do not protect against this — that's a markdown convention, meaningless to SMF's BBC parser, which runs before markdown rendering ever happens. Wrap any literal [md]/[/md] in [nobbc]...[/nobbc], or HTML-entity-encode the brackets ([md]), before posting.

Forum BBC policy: all standard BBCode tags are disabled on this forum except [nobbc] (kept enabled/exempt — it's the escape hatch that makes the warning above possible to follow) and [md] itself (the tag this whole document is about). Every other bracket-tag typed in a post is inert, literal text.

Element Raw source (unrendered) Rendered output
Heading (ATX) # H1###### H6 <h1>H1</h1><h6>H6</h6>
Heading (Setext) Title\n===== / Title\n----- <h1>Title</h1> / <h2>Title</h2>
Bold **bold** or __bold__ <strong>bold</strong>
Italic *italic* or _italic_ <em>italic</em>
Bold + italic ***both*** or ___both___ <em><strong>both</strong></em>
Strikethrough ~~struck~~ <del>struck</del>
Inline code `code` <code>code</code>
Inline code (contains a backtick) code with inside` `` <code>code with inside</code>`
Fenced code block ``php/ code / ` (also~~~`) <pre class="md-code-block"><code class="language-php">...</code></pre> (the class="language-X" only appears when a language is specified after the fence)
Indented code block 4 spaces or a tab before each line (blank lines inside stay part of the same block) <pre class="md-code-block"><code>...</code></pre>
Blockquote > quoted (nested: >> quoted) <blockquote><p>quoted</p></blockquote>
Unordered list (tight) - item / * item / + item <ul><li>item</li></ul>
Unordered list (loose — blank line between items) - one\n\n- two <ul><li><p>one</p></li><li><p>two</p></li></ul>
Ordered list 1. item or 1) item <ol><li>item</li></ol>
Ordered list, custom start 7. item <ol start="7"><li>item</li></ol>
List marker change starts a new list - foo\n- bar\n+ baz two separate <ul> elements (different bullet char = different list)
Ordered list can't interrupt a paragraph unless it starts at 1 Some text\n14. more text stays one <p>, not a <p> + <ol start="14">
Empty list item (bare marker, nothing after it) - alone on a line <ul><li></li></ul>
Link [text](url "title") <a href="url" title="title" rel="noopener noreferrer nofollow">text</a>
Link title, alternative delimiters [text](url 'title') or [text](url (title)) same as above
Link text with nested brackets [link [foo] bar](url) <a href="url">link [foo] bar</a>
Link, empty URL [text]() <a href="">text</a>
Link, angle-bracket destination (allows spaces) [text](<my file.pdf>) <a href="my%20file.pdf">text</a>
Link destination, balanced parens [text](foo(and(bar))) <a href="foo(and(bar))">text</a>
Link destination, percent-encoding [text](foo b&auml;) <a href="foo%20b%C3%A4">text</a> (HTML entities decoded, then unsafe bytes percent-encoded; existing %XX sequences left alone)
Image ![alt](url "title") <img src="url" alt="alt" title="title">
Autolink (angle brackets) <https://example.com> or <user@example.com> <a href="https://example.com" rel="noopener noreferrer nofollow">https://example.com</a> (mailto autolinks omit rel)
Bare URL autolink (GFM ext.) plain https://example.com in running text auto-linked, trailing .,!?: etc. left outside the link
Bare www. autolink (GFM ext.) plain www.example.com in running text linked with href="http://www.example.com"
Bare email autolink (GFM ext.) plain name@example.com in running text <a href="mailto:name@example.com">name@example.com</a>
Horizontal rule ---, ***, or ___ alone on a line <hr>
Hard line break line ending in two trailing spaces <br>
Paragraph break blank line between blocks separate <p> elements
Escaping 0not italic1 — any ASCII punctuation character can be escaped this way, not just markdown-syntax ones *not italic* (literal)
GFM table <code>| a | b |<br>|---|---:|<br>| 1 | 2 |</code> <table class="md-table"> with style="text-align:right" etc. from the :---/---:/:---: header row

Forum-specific extensions (not part of GFM)

Element Raw source (unrendered) Rendered output
Wikipedia link {wiki:Article Name} <a href="https://en.wikipedia.org/wiki/Article_Name" rel="noopener noreferrer nofollow">Article Name</a> — same tab, no target="_blank"

Exclusions & Gaps

Everything the parser doesn't do, grouped by why — because "not spec-complete" covers two very different situations: things we chose not to build, and things security requires us not to build.

Security-motivated exclusions (deliberate — do not "fix" these for conformance)

Excluded Reason
Raw HTML blocks (multi-line embedded HTML in a post) The mod's entire security model is "escape everything on input, then selectively re-decode only what's explicitly recognized as markdown syntax." Parsing raw HTML blocks back into the page means accepting arbitrary user-supplied HTML — exactly the attack surface the last external security review closed (the stored-XSS-via-code-span and control-character URL bypass findings). Re-opening this for GFM conformance would undo that work.
Autolinking arbitrary URI schemes (<irc://...>, <made-up-scheme://...>) isSafeUrl() only allows http, https, ftp, ftps, mailto. CommonMark's actual rule permits any scheme matching [a-zA-Z][a-zA-Z0-9+.-]{1,31}: — a superset that, unfiltered, would also cover javascript:, data:, vbscript:. Broadening the allowlist to add specific safe schemes (irc://, magnet:, etc.) is a legitimate future option, but it's a deliberate allowlist decision to make each time, not something that should get widened automatically in the name of spec coverage.

Scope decisions (no security angle — just low value for a forum)

Excluded Reason
Reference-style links [text][ref] + [ref]: url Adds a second-pass resolution step for a syntax forum posters essentially never use
Footnotes Same reasoning as reference-style links
Task-list checkboxes - [ ] Low forum-relevance extension

Known remaining gaps (real bugs, still open — no security implication)