How-to
Markdown to Gutenberg: What Actually Happens When Your AI Writes for WordPress
Converting markdown to Gutenberg requires translating markdown syntax into Gutenberg's specific block grammar, which relies on HTML comments. If you send raw markdown or plain HTML to the WordPress REST API, WordPress wraps the entire payload in a single Classic block. To get native Gutenberg blocks—like distinct paragraphs, headings, and lists—you must parse the markdown into HTML and wrap each element in tags like <!-- wp:paragraph -->. The challenge is that Gutenberg's block validation is strictly enforced; even a single misplaced HTML tag inside these comments will trigger an "invalid block" warning.
Why does sending raw markdown to WordPress fail?
When you use an AI agent to write content, it naturally outputs markdown. This lightweight markup language is perfect for LLMs like Claude or ChatGPT. However, WordPress does not natively speak markdown.
If you push raw markdown directly to the standard WordPress REST API, the system does not know how to parse it. Instead of creating distinct blocks, WordPress falls back to its default behavior. It dumps your entire markdown string into a single Classic block.
Even if you convert the markdown to standard HTML before sending it to the API, the result is the same. WordPress sees a massive block of continuous HTML and creates one giant HTML block or Classic block. Your content will display correctly on the front end, but it becomes completely unmanageable in the block editor. To edit the post later, you would have to manually convert that monolithic block into individual native blocks. This defeats the purpose of using the modern WordPress editor.
What is Gutenberg block grammar?
To successfully map markdown to Gutenberg, you have to understand how Gutenberg stores data. Unlike the old WordPress editor that saved standard HTML, Gutenberg uses a specialized format called block grammar.
Block grammar relies on HTML comments to define the start and end of a block. These comments tell the WordPress editor exactly which block to load and what settings to apply.
A standard paragraph in Gutenberg looks like this in the database:
<!-- wp:paragraph -->
<p>This is a paragraph.</p>
<!-- /wp:paragraph -->
Notice that the actual HTML content (<p>) is sandwiched between the opening and closing block comments. This structure is mandatory. If you miss the closing comment, the block breaks.
More complex blocks include JSON attributes inside the opening comment. For example, a heading block with a specific level and text alignment looks like this:
<!-- wp:heading {"textAlign":"center","level":3} -->
<h3 class="has-text-align-center">My Subheading</h3>
<!-- /wp:heading -->
When converting markdown to Gutenberg, your parser must generate these exact comment wrappers. It must also inject the correct JSON attributes and ensure the interior HTML perfectly matches what the block expects. If the JSON attributes say it is an H3, but the HTML tag is an H2, WordPress will reject it.
Why do converters produce "Invalid Block" warnings?
Writing a custom script to push AI content often results in the dreaded "This block contains unexpected or invalid content" warning. This is caused by Gutenberg's strict validation process.
When you open a post in the WordPress editor, Gutenberg reads the block grammar from the database. It then runs the block's save function in JavaScript to regenerate the expected HTML based on the JSON attributes in the block comment. Finally, it compares this freshly generated HTML against the HTML that was actually saved in the database.
If these two HTML strings do not match exactly, Gutenberg flags the block as invalid. This validation is notoriously unforgiving.
If your markdown-to-HTML converter adds an extra newline character inside a <p> tag, the block might fail validation. If you include an allowed HTML attribute but place it in the wrong order, the block fails.
For example, if your converter outputs <ul class="my-list"> but the standard List block expects just <ul>, Gutenberg will throw a block recovery error. To build a robust converter, you cannot just write valid HTML. You must write the exact flavor of HTML that Gutenberg expects for every single block type.
How to map markdown syntax to Gutenberg blocks
To get native blocks, you need a precise mapping between markdown elements and WordPress block comments. Here is how standard markdown elements translate to their Gutenberg equivalents.
| Markdown Syntax | HTML Equivalent | Gutenberg Block Comment |
|---|---|---|
Text |
<p>Text</p> |
<!-- wp:paragraph --> |
## Heading |
<h2>Heading</h2> |
<!-- wp:heading {"level":2} --> |
- Item |
<ul><li>Item</li></ul> |
<!-- wp:list --> |
1. Item |
<ol><li>Item</li></ol> |
<!-- wp:list {"ordered":true} --> |
> Quote |
<blockquote>Quote</blockquote> |
<!-- wp:quote --> |
--- |
<hr> |
<!-- wp:separator --> |
```code``` |
<pre><code>code</code></pre> |
<!-- wp:code --> |
 |
<img src="url" alt="alt"> |
<!-- wp:image --> |
Every time your parser encounters one of these markdown elements, it must wrap the resulting HTML in the corresponding block comment.
Building a manual markdown to Gutenberg parser
Building this conversion requires chaining a markdown parser with a custom HTML transformer. Developers usually start with a library like Marked.js to convert raw markdown into HTML. From there, you must traverse the HTML tree and wrap each node in the correct block grammar.
For a basic implementation, you would write a script that looks at the tag name of each HTML element. If the tag is an <h2>, you prepend <!-- wp:heading {"level":2} --> and append <!-- /wp:heading -->. If the tag is a <table>, you wrap it in <!-- wp:table -->.
However, handling nested elements makes this complicated. Gutenberg's list block is particularly tricky. In older versions of WordPress, the entire <ul> was wrapped in a single block comment. In newer versions, the <ul> is wrapped in a list block, but each individual <li> is also treated as an inner block with its own <!-- wp:list-item --> wrapper.
If your custom script just wraps the outer <ul> without adding the inner block comments for the list items, the list will trigger a validation error in modern WordPress. You have to constantly update your parser to match the evolving block grammar of WordPress core updates.
Why image and embed conversions usually break
Text is relatively easy to parse, but media is where manual markdown to Gutenberg scripts usually fall apart.
When an AI writes markdown, it might include an inline image like . If your parser simply wraps this in an <!-- wp:image --> block and sends it to the REST API, the image will load on the front end. However, the image is now hotlinked from the external URL. It is not in your WordPress media library. If the source URL goes down, your site has a broken image.
To do this correctly, your script must intercept the image URL, download the file, upload it to the WordPress media library via the REST API, get the new attachment ID, and then construct a Gutenberg image block using that specific ID. For a deep dive into how media files should be handled, check our guide on WordPress image handling.
Embeds are equally frustrating. If an AI generates a YouTube URL on a blank line, a basic markdown parser might wrap it in a paragraph block. WordPress will just display it as clickable text. To get a native video player, your parser must recognize the URL, check if it matches a known oEmbed provider, and wrap it in <!-- wp:embed {"providerNameSlug":"youtube"} -->. You can see the full list of supported providers in our embeds documentation.
What happens when your AI writes for WordPress?
Modern AI agents like Claude, ChatGPT, and tools built on n8n are excellent at writing markdown. But because they cannot securely connect to your WordPress database or natively generate flawless block grammar on their own, they need a bridge.
This bridge is often a Model Context Protocol (MCP) server. An MCP server acts as a specialized tool that an AI agent can call. Instead of forcing the AI to figure out WordPress authentication, REST API endpoints, and block validation, the AI simply passes its standard markdown to the MCP server.
The server takes on the burden of translating that markdown into native Gutenberg blocks, handling media uploads, and communicating securely with the WordPress site. This abstraction means the AI can focus on writing high-quality content, while the server handles the strict technical requirements of the WordPress editor.
How Pressbotics translates markdown to native blocks
Maintaining a flawless markdown-to-Gutenberg parser is painful. Pressbotics provides a hosted MCP server (a "rail") letting AI agents publish natively to self-hosted WordPress sites.
The agent sends standard markdown to Pressbotics, which automatically converts it to native Gutenberg blocks. This covers headings, paragraphs, bulleted and numbered lists, tables, quotes, pull quotes with citations, cover blocks, columns, galleries, code blocks, separators, and images with captions. Posts open in the editor as real blocks.
Pressbotics also handles complex edge cases. If the AI provides an image URL, Pressbotics can attach it as a featured image, or you can use an upload link to manually add a photo. Inline images that cannot be fetched are gracefully removed and listed in the publish report; Pressbotics does not silently hotlink broken images.
Embeds are mapped automatically. A YouTube watch URL, Spotify, or TikTok URL on its own line becomes an embedded player. A self-hosted .mp4 becomes a WordPress video block. Because Twitter/X and Instagram retired their open oEmbed endpoints, those URLs safely render as plain links.
Every publish returns an honest report to the agent, detailing the exact state of the post, inline images, embeds, and a final render check to ensure the live page looks right.
If you want your AI agents to publish perfectly formatted native blocks without writing a custom parser, you can try the Pressbotics free plan. Check out the details on our pricing page or sign up to connect your first site today.
Frequently asked questions
- Why does my markdown text show up as a Classic block in WordPress?
- When you send raw markdown or plain HTML directly to the WordPress REST API, WordPress does not recognize any block grammar. To prevent the content from breaking, it safely wraps the entire payload inside a single Classic block. You must convert the markdown into HTML comments designed for Gutenberg to get native blocks.
- Can I just use standard HTML instead of Gutenberg block grammar?
- No, standard HTML is not enough for the modern WordPress editor. If you send standard HTML without the required HTML comment wrappers (like <!-- wp:paragraph -->), WordPress will simply treat the entire submission as a single HTML block. You will not be able to edit individual paragraphs or headings easily.
- How do I fix the invalid block warning in WordPress?
- The "invalid block" warning occurs when the HTML inside a block does not perfectly match what the block's internal JavaScript save function expects. To fix it, you must ensure your markdown converter outputs the exact HTML tags, classes, and JSON attributes required by the current version of WordPress for that specific block.
- Does WordPress support embedding YouTube videos from markdown?
- Yes, if your parser is configured correctly. A raw YouTube watch URL placed on its own line in markdown must be converted into a specific Gutenberg embed block before sending it to the REST API. If it is just wrapped in a paragraph tag, it will render as a clickable text link.

