Discourse to Markdown: Serve Your Forum to AI Agents

I built roots/discourse-to-markdown, a Discourse plugin that does content negotiation on every forum route. Send Accept: text/markdown or append .md to any URL and you get Markdown back instead of HTML.

What it does

Every route a reader hits has a Markdown equivalent:

Route HTML Markdown
Topic /t/:slug/:id /t/:slug/:id.md
Single post /t/:slug/:id/:post_number /t/:slug/:id/:post_number.md
Category /c/:slug/:id /c/:slug/:id.md
Tag /tag/:tag /tag/:tag.md
Latest /latest /latest.md
Top /top /top.md
Hot /hot /hot.md
User activity /u/:username/activity /u/:username/activity.md

Two ways to ask for it:

# Accept header — for programmatic clients
curl -H "Accept: text/markdown" https://discourse.roots.io/t/welcome/5

# .md URL suffix — shareable, paste-into-chat friendly
curl https://discourse.roots.io/t/welcome/5.md

Content negotiation gotchas

Rails’ Accept handling doesn’t rank text/markdown above */*, so Accept: text/markdown, */* gets you HTML. The plugin ships its own parser that ranks by specificity, the way RFC 9110 says it should.

It also sets Vary: Accept on every response. Without it, a CDN can cache the HTML for a given URL and serve it to an agent asking for Markdown, or vice versa.

Cooked, not raw

Discourse stores two versions of every post: raw (the authoring syntax, with [quote=…] blocks and :smile: shortcodes) and cooked (the rendered HTML, with oneboxes expanded, mentions linked, and quotes attributed). The plugin converts cooked to Markdown so the output matches what readers actually see.

Discourse-specific constructs are rewritten before conversion:

  • <aside class="quote"> → blockquote with > [@user](post-url): attribution
  • <aside class="onebox"> → blockquote with title, URL, and excerpt
  • <details><summary> → blockquote with bolded summary + body
  • <a class="mention">@user</a>@user literal
  • <div class="lightbox-wrapper"> → image with the full-size URL

Install

Add the plugin to your app.yml:

hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - git clone https://github.com/roots/discourse-to-markdown.git

Then rebuild:

cd /var/discourse
./launcher rebuild app

Flip discourse_to_markdown_enabled on in Admin → Settings → Plugins and every route above starts negotiating on Accept: text/markdown (and exposes a .md suffix as a fallback).


More context at acceptmarkdown.com, including a pass/fail readiness check for your own site. If you’re on WordPress instead of Discourse, post-content-to-markdown does the same job there.