fundamentals

The Accept: text/markdown convention

What agent clients send, what your server should match on, and the exact media-type string defined by RFC 7763.

The media type registered with IANA for Markdown is text/markdown, defined in RFC 7763. That’s the string clients should send in Accept, and the string your server should send in Content-Type.

What agent clients actually send

Most well-behaved agent clients send something like one of these:

Accept: text/markdown
Accept: text/markdown, text/html;q=0.8
Accept: text/markdown, text/plain;q=0.5, */*;q=0.1

The first preference is text/markdown. If the server can’t do that, the client falls back to HTML or plain text. The q=0.8 means “accept HTML at 80% of the preference given to Markdown.”

Some clients are lazier and send Accept: text/plain or */*. Those aren’t asking for Markdown specifically, so your server should serve HTML (or whatever your default is).

What your server should match on

Don’t substring-match on text/markdown. A naive accept.includes("text/markdown") happens to work, but accept.startsWith("text/html") breaks on real Chrome headers. Use a proper Accept parser — see Accept parsing & q-values for what to watch for.

The short version: parse the header into an ordered list of type + q-value, sort by q, break ties by specificity (more specific wins over wildcards), and pick the highest-ranked type you can produce.

Content-Type on the response

If you served Markdown, say so:

Content-Type: text/markdown; charset=utf-8

The charset=utf-8 parameter is a good idea — Markdown is just text, but explicit is safer.

Optional: the variant parameter

RFC 7764 defines a variant parameter for distinguishing Markdown flavors:

Content-Type: text/markdown; charset=utf-8; variant=CommonMark
Content-Type: text/markdown; charset=utf-8; variant=GFM

The variant parameter is uncommon in public traffic, and most clients don’t request it. You don’t need it. But if you’re being thorough about which flavor of Markdown you serve, it’s there.

What not to use

  • text/x-markdown — deprecated, predates the registration
  • application/markdown — not registered, not conventional
  • .md in the URL path (/article.md as a sibling of /article) is fine to ship, but agents have no way to discover it. They hit the canonical URL — /article — and read whatever it returns. If that’s HTML, HTML is what ends up in their context.
  • /llms.txt is sometimes proposed as a way to advertise sibling URLs to agents, but adoption in the wild is thin — don’t count on it.
  • Content negotiation via Accept: text/markdown on the canonical URL doesn’t require discovery. The agent hits the URL it already knows. Your server picks the right bytes. Every HTTP client speaks Accept — that’s the difference.
  • Ship .md siblings if you want; they don’t hurt. Just don’t rely on them as the only path.

If you do ship siblings and want to make them discoverable, advertise them per RFC 8288 — either as an HTTP Link header or an HTML <link> element:

Link: </article.md>; rel="alternate"; type="text/markdown"
<link rel="alternate" type="text/markdown" href="/article.md">

Both declare “the same resource is also available at this URL in this format.” Agents and search engines that parse rel="alternate" can follow it. Adoption in real-world agents is thin today, but the markup is spec-compliant and costs nothing.

This is complementary to content negotiation, not a substitute. If you have both, hitting /article with Accept: text/markdown still works for any agent; rel="alternate" just gives the ones that look for it a second path.