Skip to content

WP Packages is our new WPackagist replacement that's 17x faster and updates every 5 minutes

  1. Blog

Serve Your WordPress Posts as Markdown

Ben Word Ben Word

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/markdown request header, for agents and scripts
  • A ?format=markdown query parameter
  • A .md URL suffix, e.g. /hello-world.md. The HTML version of the post advertises this URL back via a Link: rel="alternate"; type="text/markdown" response header and a matching <link> in <head>, so agents that don’t yet support the text/markdown Accept 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/ with Accept: text/markdown returns the main feed as Markdown
  • /post-slug/feed/ with Accept: text/markdown returns 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_types opts pages or custom post types in (default: ['post'])
  • post_content_to_markdown/post_allowed is a per-post allowlist that runs after the post type check
  • post_content_to_markdown/converter_options controls header style, hard breaks, and removed nodes
  • post_content_to_markdown/conversion_cache_duration shortens the TTL if you have request-aware blocks
  • post_content_to_markdown/markdown_output is 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.0 correctly prefers Markdown
  • Vary: Accept is 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 Acceptable is returned when the client’s Accept header rules out every representation the site can serve, instead of silently falling back to HTML
  • X-Markdown-Source: accept | md-url | query is set on Markdown responses so you can see in your access logs how clients are asking
  • .md URL responses include X-Robots-Tag: noindex, nofollow so 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, and 406 Not Acceptable
  • v1.6 (#11) adds the .md URL suffix with Link and <link> advertising
  • v1.7 (#12) adds object-cache memoization, the <atom:link> autodiscovery in RSS, and the X-Markdown-Source response 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.

About the author

Ben Word

Ben Word has been creating WordPress sites since 2004. He loves dogs, climbing, and yoga, and is passionate about helping people build awesome things on the web.

Subscribe for updates

Join over 8,000 subscribers for the latest Roots updates, WordPress plugin recommendations, modern WordPress projects, and web development tips.

One last step! Check your email for a verification link.