| name | markdown-plus-plus |
| description | AUTHORITATIVE REFERENCE for Markdown++ syntax. Use when reading or writing .md files that contain any of these signals (whitespace inside the HTML comment is irrelevant): <!--style:--> directives, <!--multiline--> table directives, <!--condition:--> blocks, <!--include:--> directives, <!--marker:--> or <!--markers:-->, <!--#alias--> anchors, $variable; tokens, or mdpp-version: in YAML frontmatter. Also use when creating a .md file modeled after another file containing any of these signals. Use for editing, validating, migrating, or auditing Markdown++ source documents.
|
markdown-plus-plus
Read and write Markdown++ documents - an extended Markdown format with variables, conditions, custom styles, file includes, and markers.
Do not use training data for Markdown++. This is an extended documentation format built on CommonMark. Markdown++ is fully backward compatible with CommonMark -- all extensions use HTML comment tags and are ignored by standard Markdown parsers. Use only this skill's references for extension syntax and behavior rules.
Overview
Markdown++ extends CommonMark with HTML comment-based extensions. All extensions (except variables) use HTML comments for backward compatibility with standard Markdown renderers.
Quick Reference
- Version Declaration:
mdpp-version: 1.0 -- Spec version in YAML frontmatter
- Variables:
$variable_name; -- Inline, reusable content
- Styles:
<!--style:Name--> -- Block (above) or Inline (before)
- Aliases:
<!--#alias-name--> -- Anchor for [text](#alias-name) links
- Conditions:
<!--condition:name-->...<!--/condition--> -- Show/hide content by format
- Includes:
<!--include:path/to/file.md--> -- Insert file contents
- Markers:
<!--markers:{"Key": "val"}--> -- Metadata for search/processing
- Multiline Tables:
<!-- multiline --> -- Enable block content in cells
<command_block_syntax>
Command Block Syntax (MUST follow exactly)
ALWAYS use this format -- no variations. Content MUST follow the directive on the very next line with no blank line between them.
Style + alias (combined)
<!-- style:StyleName ; #anchor-name -->
Content immediately follows (NO blank line).
Markers + alias (combined)
<!-- markers:{"Keywords": "word1, word2", "Description": "Brief description"} ; #anchor-name -->
## Heading immediately follows (NO blank line).
Single-directive forms
<!-- style:StyleName -->
Content immediately follows.
<!-- marker:Keywords="..." -->
Content immediately follows.
<!-- #anchor-name -->
Content immediately follows.
NEVER
- Put a blank line between a directive comment and its target content. The blank line silently breaks the attachment rule; the directive passes through as a regular HTML comment with no effect.
- Place the alias on a separate line from
style: or markers:. Stacked HTML comments leave the top one orphaned -- combine them with ; in a single directive instead.
- Vary the spacing inside
<!-- ... --> or around ; arbitrarily. The canonical form has a space after <!--, a space before -->, and spaces around each ;. The no-space form (<!--style:A;#b-->) is also valid; the spaced form is recommended for readability per references/syntax-reference.md (Combined Commands -> Whitespace).
See references/syntax-reference.md (Attachment Rules, Combined Commands) and the <common_mistakes> section below for the full rule set and edge cases. See ../../../../spec/attachment-rule.md for the normative definition.
</command_block_syntax>
<syntax_examples>
Syntax Examples
Variables
Variables store reusable values across documents. They use $name; syntax (dollar sign, name, semicolon).
Welcome to $product_name;, version $version;.
The **$product_name;** application supports...
Rules:
- Names must follow Naming Rules (letter or underscore first)
- Must end with semicolon
- No spaces in variable names
- Case-sensitive:
$Product; differs from $product;
- Escape with
\$ to prevent variable interpretation, or use inline code (`$name;`)
Valid: $product_name;, $version-2;, $my_var;
Invalid: $product name; (space), $product (no semicolon)
Variable values: Values can contain Markdown syntax (**bold**, [link](url)) — it renders normally. Values cannot contain condition or include directives — those are resolved before variable substitution. See spec/processing-model.md for processing order.
Custom Styles
Styles override default formatting. Placement depends on element type.
Block-level (place on line directly above element, no blank line):
<!--style:CustomHeading-->
# My Heading
<!--style:NoteBlock-->
> This is a styled blockquote.
IMPORTANT: Block commands must be attached to the element (no blank line between). A blank line breaks the association and passes through as a regular Markdown comment with no Markdown++ association/effect.
Inline (place immediately before the element, no space):
This is <!--style:Emphasis-->**important text**.
See references/syntax-reference.md for nested list indentation rules and table styling.
Custom Aliases
Aliases create stable internal link anchors. Aliases are block-level only -- they must appear on their own line above a block element, not inline.
<!--#getting-started-->
## Getting Started
See [Getting Started](#getting-started) for an introduction.
Cross-document links: [API Reference](api.md#authentication)
Rules:
- Block-level only -- must be on its own line above a block element (inline aliases are not supported)
- Names must follow Naming Rules (digit-first allowed for aliases)
- No spaces (alias ends at first space)
- Must start with
# inside the comment
- Keep alias values unique within each file
- Must be attached to an element (no blank line between alias and element) -- same rule as styles
Auto-generated alias collisions: When multiple headings produce the same auto-generated alias, the first heading keeps the bare alias and subsequent headings receive -2, -3, etc. (e.g., setup, setup-2, setup-3). Custom aliases and auto-generated aliases occupy separate namespaces -- when a custom alias and an auto-generated alias share the same identifier, both exist independently, but the custom alias wins at link resolution time (no suffixing or displacement of the auto-generated alias). Both overlap types are resolved silently -- no diagnostic is emitted. Custom alias duplicates remain an error (MDPP008). When a heading with a suffixed auto-generated alias also has a custom alias, both are valid anchors (e.g., #db-setup and #setup-2). See spec/element-interactions.md for the full algorithm.
Recommended pattern for referenceable headings: When authoring or editing a Markdown++ topic file, apply the alias+slug+linkref triple (see GLOSSARY.md) on every heading meant to be externally referenceable:
<!-- style:Heading2; #200020 -->
## Installation
[installation]: #200020 "Installation"
- When the rule fires: the file's title (H1), and H2 headings that name a major section a reader is likely to deep-link to.
- What to author: all three pieces, adjacent in source -- the
<!-- style:HeadingN; #target --> directive on the line directly above the heading, the heading itself, and a [semantic-slug]: #target "Title" link reference definition on the line directly after the heading (separated by a single blank line). Do not migrate the link reference definition to a block at the bottom of the file. Just an alias (without the paired definition) makes the heading internally addressable but does not establish a semantic cross-reference. The slug may be a separate semantic name (as in the example above) or the alias value itself when the alias is already semantic and unique. See the Choosing the slug guidance in references/best-practices.md § Semantic Cross-References on Topic-Defining Headings.
- When the rule does NOT fire: H3+ headings are author judgement -- do not apply the triple reflexively to every subsection. A custom alias used purely as an internal anchor (no external references expected) is also valid on its own.
The triple gives cross-context resolution (standalone, single-file, assembly), section-move safety through co-location, and -- for automation pipelines that mint unique anchors -- a single identifier per heading via the slug = alias variant. See Semantic Cross-References on Topic-Defining Headings for the full rationale and the inline-anchor-vs-triple comparison.
Use scripts/add-aliases.py to auto-generate aliases for headings. The script writes alias anchors only -- it does not author the paired link reference definitions, so you must add those by hand when the heading is meant to be externally referenceable.
Conditions
Conditions show or hide content based on output format.
<!--condition:web-->
Visit our [website](https://example.com) for updates.
<!--/condition-->
Operators: Space (AND), Comma (OR), Exclamation (NOT). Precedence: NOT > AND > OR.
<!--condition:!draft,web production-->
Means: (!draft) OR (web AND production)
<!--/condition-->
Inline: Contact us at <!--condition:web-->email<!--/condition--><!--condition:print-->the back cover<!--/condition-->.
Per-file scoping: Condition blocks must open and close within the same file — they cannot span across an include boundary (MDPP012). See spec/processing-model.md.
Condition state model: Each condition name is either Visible or Hidden (assigned states); a name not defined in the condition set is Unset (see GLOSSARY.md) — not an assigned state, but the absence of one. Unset conditions are not evaluated — the condition block passes through as-is (opening tag, content, and closing tag preserved), allowing the implementation to surface or resolve undefined conditional content downstream. In compound expressions (NOT/AND/OR), if any operand is Unset, the entire block passes through without evaluation.
Variable substitution inside Unset blocks: "Passes through as-is" refers to condition evaluation only. Variable substitution (Phase 1, Step 2) still resolves $variable; tokens inside Unset blocks because the content survives into that processing step. Include directives inside Unset blocks are NOT processed — the include tag passes through as literal text.
File Includes
Insert content from other Markdown++ files.
<!--include:shared/header.md-->
Rules: Paths relative to containing file. Recursive includes supported; circular includes detected and prevented. Must be alone on its line. Can be wrapped in conditions.
Cross-file link resolution: After include expansion, link reference definitions ([slug]: url) from all files share a single document-global scope. When multiple files define the same slug, first-definition-wins (per CommonMark 0.30) and include order determines priority. Cross-file duplicate slugs emit MDPP014 (warning). See spec/cross-file-link-resolution.md for full rules.
Markers (Metadata)
Attach metadata to document elements for search, processing, or custom behavior.
Single key-value:
<!--marker:Keywords="api, documentation"-->
JSON format (multiple keys):
<!--markers:{"Keywords": "api, documentation", "Description": "API reference guide"}-->
Index markers create entries in generated indexes:
<!--marker:IndexMarker="creating projects"-->
## Creating Projects
Format: primary for top-level, primary:secondary for nested, comma-separated for multiple entries. See references/syntax-reference.md for detailed marker examples.
Multiline Tables
Enable block content (lists, blockquotes, styled elements) inside table cells.
<!-- multiline -->
| Name | Details |
|------|---------|
| Bob | Lives in Dallas. |
| | - Enjoys cycling |
Every pipe-bearing row continues the current logical row. A row whose cells are all whitespace starts a new logical row — that's the only way to signal one. A no-pipe blank line ends the table. Cells on a continuation line appear empty when their column has no more content to flow there. Combine with style: <!-- style:DataTable ; multiline -->. See references/syntax-reference.md for multiline table rules.
Extensions in cells: Variables, block/inline styles, markers, conditions (conditional rows only — wrap complete rows or the whole table; don't author in-cell condition spans, and never a conditional cell that hides a | delimiter; MDPP019), and combined commands work inside multiline table cells. Includes and nested multiline tables are not supported in cells. See spec/multiline-cell-extensions.md.
Combined Commands
Multiple commands in a single comment, separated by semicolons. Order: style, multiline, marker(s), #alias.
<!-- style:CustomHeading ; marker:Keywords="intro" ; #introduction -->
# Introduction
Inline Styling for Images and Links
<!--style:CustomImage-->
[<!--style:CustomLink-->*Link text*](topics/file.md#anchor "Title")
Content Islands (Blockquotes)
Blockquotes with custom styles create configurable content islands (see GLOSSARY.md) for callouts and notes.
<!--style:BQ_Warning-->
> **Warning:** This is a styled warning block.
>
> Take note of the following:
> 1. First consideration
> 2. Second consideration
Nested Lists with Styling
<!--style:ProcedureList-->
1. First step
- Sub-item A
- Sub-item B
2. Second step
3. Third step
Document Structure
Topic map pattern -- use includes to organize multi-chapter documents with conditional sections. See references/examples.md (Example 3) for a complete topic map example.
</syntax_examples>
Validation
Use the validation script to check Markdown++ syntax:
python scripts/validate-mdpp.py document.md
Options:
--verbose - Show detailed output
--json - Output errors as JSON
--strict - Treat warnings as errors
Common errors detected (see Error Code Reference for details):
- Unclosed condition blocks
- Invalid names — variables, styles, aliases, marker keys (see Naming Rules)
- Malformed marker JSON
- Circular file includes
- Duplicate alias values within a file
- Orphaned comment tags (tag not attached to element)
- Duplicate link reference slugs across included files (MDPP014)
- Multiline tables with no separator rows that silently merge data rows (MDPP018)
- Condition open/close tags inside a table row line — in-cell spans or conditional cells (MDPP019)
Alias Generation
Generate unique aliases for headings:
python scripts/add-aliases.py document.md --levels 1,2,3
Options:
--levels - Comma-separated heading levels to process (e.g., 1,2,3)
--dry-run - Preview changes without modifying file
--prefix - Add prefix to generated aliases
See references/syntax-reference.md for complete syntax rules.
<common_mistakes>
Common Mistakes
1. Blank Line Breaks Tag Attachment (The Attachment Rule)
This is the #1 source of Markdown++ errors. A blank line between a comment tag and its target element silently breaks the association. The tag passes through as a regular HTML comment with no visible error. This applies to styles, aliases, markers, and combined commands equally. Only conditions (which wrap content) and includes (standalone directives) are exempt.
Wrong -- blank line breaks attachment:
<!-- style:NoteBox -->
> This blockquote will NOT receive the style.
Right -- tag directly above target, no blank line:
<!-- style:NoteBox -->
> This blockquote receives the style.
Wrong -- tag below content (tags attach downward only):
## Getting Started
<!-- #getting-started -->
Right -- tag above content:
<!-- #getting-started -->
## Getting Started
Wrong -- stacked tags (top tag is orphaned):
<!-- style:CustomHeading -->
<!-- #my-alias -->
## Heading
Right -- combine with semicolons:
<!-- style:CustomHeading ; #my-alias -->
## Heading
See the Attachment Rule specification for the formal definition and all edge cases.
2. Mismatched Indentation on Nested Lists
Indentation of Markdown++ comment tags must match the content line. In nested lists, if the comment tag is indented but the following content is not (or vice versa), the style passes through as a regular Markdown comment and is not applied.
3. Missing Semicolon on Variables
Variables without a trailing semicolon are not recognized. $product_name is literal text; $product_name; is a variable reference. The semicolon is required.
4. Cosmetic multiline Directive Silently Merges Rows
Use multiline only when a cell needs block content or wraps across lines; single-line cells should use a plain table. Under <!-- multiline -->, every pipe-bearing row continues the current logical row -- a new logical row begins only on a whitespace-only separator row. Add the directive cosmetically to a table of single-line rows and every data row silently collapses into one logical row. The syntax is valid, so no error is raised; the failure shows up only in the rendered output. The validator flags this pattern as MDPP018 (warning).
Wrong -- no separator rows (renders as ONE row):
<!-- multiline -->
| Setting | Default |
|---------------------|---------|
| Generate Assistant | true |
| Hide AI Tab | true |
Right -- single-line cells, so drop the directive (plain table):
| Setting | Default |
|---------------------|---------|
| Generate Assistant | true |
| Hide AI Tab | true |
Right -- keep multiline and add a whitespace-only separator row between records:
<!-- multiline -->
| Setting | Default |
|---------------------|---------|
| Generate Assistant | true |
| | |
| Hide AI Tab | true |
See MDPP018 -- Multiline Table Row Merge for the detection logic.
5. Conditions in Tables Below Row Granularity
Conditional rows are the supported granularity for conditions in tables. A condition block MAY wrap complete physical rows (standard table) or complete logical rows including the trailing whitespace-only separator row (multiline table), or the whole table. Two finer patterns are authoring anti-patterns the validator flags as MDPP019 (warning): a conditional cell (a condition span containing an unescaped | delimiter) MUST NOT be authored -- hiding it removes cell boundaries and corrupts the row's column count; an in-cell condition span (opening and closing within one cell on one line) SHOULD NOT be authored -- it resists line wrapping and corrupts the table if a wrapping tool splits it across lines. Use conditional row variants for structural differences and variables for value substitution.
Wrong -- in-cell condition spans (MDPP019):
<!-- multiline -->
| Platform | Shortcut |
|----------|---------------------------------------------------|
| Save | <!--condition:mac-->Cmd+S<!--/condition--><!--condition:win-->Ctrl+S<!--/condition--> |
Right -- condition-wrapped whole rows (separator row inside the block):
<!-- multiline -->
| Platform | Shortcut |
|----------|----------|
<!--condition:mac-->
| Save | Cmd+S |
| | |
<!--/condition-->
<!--condition:win-->
| Save | Ctrl+S |
| | |
<!--/condition-->
See MDPP019 -- Condition Inside a Table Cell for the detection logic.
</common_mistakes>
Reference Files
references/syntax-reference.md - Detailed syntax rules, edge cases, and validation codes
references/error-codes.md - Error code reference with detection logic, trigger examples, and fixes
references/examples.md - Real-world document examples
references/best-practices.md - Usage guidance, naming conventions, and common mistakes
references/table-formatting.md - Readable-table conventions and the format-tables.py rule set
references/comment-manipulation.md - Rules for safely removing or simplifying Markdown++ directive comments during cleanup or migration
../../../../spec/specification.md - Consolidated Markdown++ 1.0 specification
../../../../spec/processing-model.md - Two-phase pipeline, variable/condition scoping, and processing order
../../../../spec/attachment-rule.md - Formal tag-to-element binding rule, edge cases, and exempt tags
../../../../spec/element-interactions.md - Style types, default names, compound naming, heading alias auto-generation and collision resolution
../../../../spec/formal-grammar.md - Formal EBNF/PEG grammar for parser implementation
../../../../spec/cross-file-link-resolution.md - Link reference scope and conflict rules across included files
../../../../spec/versioning.md - Format version declaration syntax (mdpp-version) and compatibility rules
../../../../spec/multiline-cell-extensions.md - Extension support and restrictions inside multiline table cells
../../../../spec/graceful-degradation.md - Graceful degradation behavior in standard CommonMark renderers
<success_criteria>
Success Criteria
- Markdown++ document uses correct syntax for all extensions
- Documents SHOULD declare
mdpp-version: 1.0 in YAML frontmatter (see Format Versioning)
- All named entities follow Naming Rules
- Conditions have matching opening and closing tags
- File includes use valid relative paths
- Markers contain valid JSON (for
markers: format)
- No circular includes detected
</success_criteria>