Markdown syntax
What the markdown converter understands, what each construct becomes, and where it quietly does something else.
The body of a document sent to POST /pages/markdown goes through a small line-based converter: each construct becomes one core Gutenberg block. It is a byte-for-byte port of the v1 PHP converter, so it is deliberately simple, and it does not report what it did not understand — an unsupported construct silently becomes paragraph text. The rules below are the whole grammar.
Headings
A line starting with one to six # and a space becomes core/heading of that level. Inline markup works inside. Do not write a level-one heading: the page title is printed by the frame from name, so the body starts at ##. A # without the space is plain text.
Paragraphs and line breaks
Consecutive non-empty lines form one paragraph, joined with <br>. A paragraph ends at an empty line or at a line that starts another block — a heading, a list item, a quote, a fence, a table row, an image, a separator or a raw block. So write one paragraph per line: a hard-wrapped paragraph keeps its wraps on the page.
First line of a paragraph.
Second line — joined to the first with a <br>.
A new paragraph.
Lists
A line starting with - , * or a number with a dot and a space (1. ) starts core/list, unordered or ordered. Every item is one line; the list ends at the first line that is not an item. There is no nesting: an indented item does not start a list and becomes paragraph text. An ordered list always counts from one, whatever numbers are written.
Tables
Consecutive lines starting with | form core/table. The first row is the header. A second row made only of dashes and colons is a separator and is skipped; alignment colons are ignored. Leading and trailing pipes are trimmed, cells get inline markup.
A pipe inside a cell always splits the cell — inline code does not protect it and there is no escape. Write “or” instead, or move the content out of the table.
Quotes and separators
Consecutive lines starting with > form one core/quote, lines joined with <br>. Only inline markup works inside: no lists or headings. A line of three or more -, * or _ and nothing else is core/separator.
A quote is fine for a short warning. For anything longer use
op/callout, below.
Images
A line holding only an image becomes core/image; a quoted title after the address becomes the caption, with inline markup:

An image with an empty address is dropped. An image with text after it on the same line, or inside a paragraph, is not converted. Use the path returned by POST /media rather than an absolute URL.
Code
A fence of three backticks with a language name opens a code block; a line of three backticks alone closes it. Everything in between is HTML-escaped and kept as is. The language, lowercased, goes into a class on the code tag:
<!-- wp:core/code -->
<pre class="wp-block-code"><code class="language-bash">curl -s "$BASE/health"</code></pre>
<!-- /wp:core/code -->
The site highlights code with Prism, and these languages are colored: bash, json, html, css, js, php, yaml. Any other name still gets its class but stays plain; a fence without a language gets a bare code and is not highlighted either. Use yaml for a markdown document with frontmatter, html for frame markup and raw blocks, json for responses, bash for calls.
Fences do not nest: the first line of three backticks inside a block closes it, so a code sample cannot contain a fence. Tilde fences and four-space indented code are not supported — they become paragraphs.
Inline markup
| Markdown | Result |
|---|---|
**bold**, __bold__ | <strong> |
*italic*, _italic_ | <em> |
| text in single backticks | <code>, contents escaped |
[text](/docs/stranicy/) | <a href> |
Inline code is taken out first, then bold, italic and links are replaced in that order. Everything outside backticks goes into the page as is, and that is where the traps are:
- A double underscore anywhere is bold, even inside a word: an unquoted frame name like
site__header__navturns its middle part bold. Single underscores are safe only between letters. - A single asterisk pairs with the next one across any distance, so two stray asterisks in a sentence italicize everything between them.
- Text is not escaped:
<tag>outside backticks becomes real HTML, and an unclosed tag breaks the rest of the page. - A backtick cannot appear inside inline code, and there are no autolinks, reference links or footnotes.
Rule of thumb: every identifier, meta key, token, frame name, tag and path goes into backticks.
Raw blocks
A line starting with <!-- wp: passes into the page untouched, which is how blocks markdown cannot express get into the text — inserts such as op/frame and op/callout included. A self-closing block is one line ending with /-->:
<!-- wp:op/frame {"ref":"article__faq"} /-->
A paired block runs until its matching closing comment, nested blocks included. The inside is not converted from markdown, so it has to be block markup itself:
<!-- wp:op/callout {"variant":"warning","title":"Rotation revokes"} -->
<!-- wp:paragraph -->
<p>Issuing a token kills the previous one immediately.</p>
<!-- /wp:paragraph -->
<!-- /wp:op/callout -->
The opener needs a space after <!-- and must start the line. Attributes are JSON, and a double quote inside a value must be escaped, otherwise the block’s JSON breaks and the attributes silently fall back to defaults. Raw blocks can also be appended through the blocks field of the request as {"type": "raw", "content": "…"}.
FAQ autodetection
With "faq_autodetect": true in the request body the converter looks for a level-two heading whose text is one of the FAQ markers — FAQ, FAQs, Frequently asked questions, Частые вопросы, Вопросы и ответы and a few more in other languages — and folds the questions under it into one onpress/faq-items block with FAQPage schema. Every level-three or level-four heading is a question; the paragraphs and lists after it are the answer.
## FAQ
### Is the token scoped to one site?
No, it carries the rights of its owner on every site of the network.
### Can a lost token be recovered?
No, only its hash is stored. Issue a new one.
<!-- wp:onpress/faq-items {"items":[{"question":"Is the token scoped to one site?","answer":"No, it carries the rights of its owner on every site of the network."},{"question":"Can a lost token be recovered?","answer":"No, only its hash is stored. Issue a new one."}]} /-->
The heading itself stays above the block. The section ends at the next level-two heading or at the first block that is neither a question nor an answer. Fewer than two questions, or a question without an answer, leave the section as ordinary headings and paragraphs. faq_markers in the body replaces the marker list. The option is off by default and is read from the request body only, not from the frontmatter.