Serve Your WordPress Posts as Markdown
LLMs and agents work better with Markdown than rendered HTML. Fewer tokens, no theme chrome to parse around. roots/post-content-to-markdown adds a Markdown representation to any WordPress site without a separate API or route.
We’ve written before about SEO plugins that advertise Markdown for AI but ignore the Accept header entirely, returning HTML to anything that asks. Post Content to Markdown does it properly: real content negotiation, block conversion, and Link headers plus <link> tags so LLMs that don’t send Accept: text/markdown can still discover the Markdown version.
Is your WordPress site AI-ready?
Run any URL through the readiness checker at acceptmarkdown.com — it’ll tell you what’s missing for agents and LLMs, with links to relevant guides. See acceptmarkdown.com/status for how the major agents stack up today.
What it does
The plugin gives every post and feed a Markdown representation that clients can request three ways:
- An
Accept: text/markdownrequest header, for agents and scripts - A
?format=markdownquery parameter - A
.mdURL suffix, e.g./hello-world.md. The HTML version of the post advertises this URL back via aLink: rel="alternate"; type="text/markdown"response header and a matching<link>in<head>, so agents that don’t yet support thetext/markdownAccept header can follow the link instead.
The same Hello world! post, two ways:
$ curl https://example.com/hello-world/
<!DOCTYPE html><html lang="en-US">…2,400 lines of theme markup…</html>
$ curl -H "Accept: text/markdown" https://example.com/hello-world/
# Hello world!
Welcome to WordPress. This is your first post. Edit or delete it, then start writing!
Conversion that doesn’t lose your content
- Renders Gutenberg blocks first, then converts, so dynamic blocks, embeds, and tables come through correctly
- Strips syntax-highlighter wrapper markup (Prism, highlight.js, etc) so code blocks aren’t full of
<span class="token …">noise - Caches the conversion at the object-cache layer (Redis/Memcached) keyed on a content hash, so repeat hits skip block rendering, shortcode expansion, and the HTML → Markdown pass entirely
Feeds and comments
The same content negotiation works on feeds:
/feed/markdown/is a dedicated Markdown feed with site metadata, post titles, authors, dates, categories, tags, and full content/feed/withAccept: text/markdownreturns the main feed as Markdown/post-slug/feed/withAccept: text/markdownreturns the post plus all of its comments
The Markdown feed is also advertised in the RSS feed via an <atom:link rel="alternate" type="text/markdown">, so feed readers and agents can discover it on their own.
Filters
Filters cover the common cases:
post_content_to_markdown/post_typesopts pages or custom post types in (default:['post'])post_content_to_markdown/post_allowedis a per-post allowlist that runs after the post type checkpost_content_to_markdown/converter_optionscontrols header style, hard breaks, and removed nodespost_content_to_markdown/conversion_cache_durationshortens the TTL if you have request-aware blockspost_content_to_markdown/markdown_outputis a final pass over the converted Markdown that runs on every request, outside the cache, so per-request customization works without invalidating cached entries
The README has the full list.
Standards-compliant content negotiation
A lot of “Markdown for AI” plugins skip the HTTP details. Post Content to Markdown follows RFC 9110 §12.5.1:
- q-values are parsed, so
Accept: text/html;q=0.9, text/markdown;q=1.0correctly prefers Markdown Vary: Acceptis emitted on every front-end response, so browsers, proxies, and CDNs key on the Accept header and don’t cross-serve an HTML body to a Markdown client- A
406 Not Acceptableis returned when the client’sAcceptheader rules out every representation the site can serve, instead of silently falling back to HTML X-Markdown-Source: accept | md-url | queryis set on Markdown responses so you can see in your access logs how clients are asking.mdURL responses includeX-Robots-Tag: noindex, nofollowso search engines don’t index the Markdown alias next to the canonical HTML page
Recent releases
Some of the recent changes to the plugin:
- v1.3 (#5) renders Gutenberg blocks before conversion and converts tables properly
- v1.5 (#8, #9) makes content negotiation spec-correct: q-value parsing,
Vary: Accept, and406 Not Acceptable - v1.6 (#11) adds the
.mdURL suffix withLinkand<link>advertising - v1.7 (#12) adds object-cache memoization, the
<atom:link>autodiscovery in RSS, and theX-Markdown-Sourceresponse header
The full history lives on GitHub.
Install it
composer require roots/post-content-to-markdown
Activate the plugin and existing posts immediately have a Markdown representation. The source is on GitHub, and issues and PRs welcome.