{"id":338045,"date":"2026-08-09T09:39:47","date_gmt":"2026-08-09T09:39:47","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/system-markdown-alternate\/"},"modified":"2026-08-09T17:51:46","modified_gmt":"2026-08-09T17:51:46","slug":"system-markdown-alternate","status":"publish","type":"plugin","link":"https:\/\/wordpress.org\/plugins\/system-markdown-alternate\/","author":20082694,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"0.37.0","stable_tag":"0.37.0","tested":"7.0.3","requires":"6.1","requires_php":"7.4","requires_plugins":null,"header_name":"System Markdown Alternate","header_author":"Diecieventi Digital Marketing","header_description":"Exposes a clean Markdown version of your posts (readable by LLMs, agents and technical tools) by appending .md to the permalink.","assets_banners_color":"747a7f","last_updated":"2026-08-09 17:51:46","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/github.com\/diecieventi\/system-markdown-alternate","header_author_uri":"https:\/\/diecieventi.com\/","rating":0,"author_block_rating":0,"active_installs":0,"downloads":45,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"0.35.3":{"tag":"0.35.3","author":"system4pc","date":"2026-08-09 09:39:11"},"0.35.4":{"tag":"0.35.4","author":"system4pc","date":"2026-08-09 14:23:30"},"0.36.0":{"tag":"0.36.0","author":"system4pc","date":"2026-08-09 16:00:17"},"0.37.0":{"tag":"0.37.0","author":"system4pc","date":"2026-08-09 17:51:46"}},"upgrade_notice":{"0.8.0":"<p>The GenerateBlocks Dynamic Tag is now always available when GenerateBlocks is\nactive; the enable\/disable toggle was removed. No action required.<\/p>","0.7.0":"<p>Integrations now appear only when ACF or GenerateBlocks are active. No action\nrequired.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3639207,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3639207,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3639207,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3639207,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["0.35.3","0.35.4","0.36.0","0.37.0"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3639207,"resolution":"1","location":"assets","locale":"","width":1208,"height":479},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3639207,"resolution":"2","location":"assets","locale":"","width":1209,"height":1025},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3639207,"resolution":"3","location":"assets","locale":"","width":1207,"height":960},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3639207,"resolution":"4","location":"assets","locale":"","width":1208,"height":674},"screenshot-5.png":{"filename":"screenshot-5.png","revision":3639207,"resolution":"5","location":"assets","locale":"","width":1207,"height":1011}},"screenshots":{"1":"Settings \u2014 General: pick the content types that expose a <code>.md<\/code> (nothing is served until at least one is ticked) and set the cache TTL. The sidebar reports the <code>\/llms.txt<\/code> status at a glance.","2":"Settings \u2014 Markdown output: what stays out of the <code>.md<\/code>. Excluded shortcodes, blocks and CSS classes (leave empty for the built-in defaults), plus the custom taxonomies added to the front matter and the ACF fields.","3":"Settings \u2014 llms.txt: enable the endpoint, the enriched output and the last modified date on each entry, then add the site summary and the curated key content.","4":"Settings \u2014 Integrations: the <code>[sysmda_md_url]<\/code> and <code>[sysmda_md_download]<\/code> shortcodes, with the GenerateBlocks and ACF detection status.","5":"Settings \u2014 Advanced: the <code>X-Robots-Tag<\/code> header, the opt-in LiteSpeed cache bypass rules and the <code>.md<\/code> hit counter, split bot vs human."}},"plugin_section":[],"plugin_tags":[2353,257149,226124,244604,4608],"plugin_category":[],"plugin_contributors":[274896],"plugin_business_model":[],"class_list":["post-338045","plugin","type-plugin","status-publish","hentry","plugin_tags-ai","plugin_tags-content-negotiation","plugin_tags-llm","plugin_tags-llms-txt","plugin_tags-markdown","plugin_contributors-system4pc","plugin_committers-system4pc"],"banners":{"banner":"https:\/\/ps.w.org\/system-markdown-alternate\/assets\/banner-772x250.png?rev=3639207","banner_2x":"https:\/\/ps.w.org\/system-markdown-alternate\/assets\/banner-1544x500.png?rev=3639207","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/system-markdown-alternate\/assets\/icon-128x128.png?rev=3639207","icon_2x":"https:\/\/ps.w.org\/system-markdown-alternate\/assets\/icon-256x256.png?rev=3639207","generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/system-markdown-alternate\/assets\/screenshot-1.png?rev=3639207","caption":"Settings \u2014 General: pick the content types that expose a <code>.md<\/code> (nothing is served until at least one is ticked) and set the cache TTL. The sidebar reports the <code>\/llms.txt<\/code> status at a glance."},{"src":"https:\/\/ps.w.org\/system-markdown-alternate\/assets\/screenshot-2.png?rev=3639207","caption":"Settings \u2014 Markdown output: what stays out of the <code>.md<\/code>. Excluded shortcodes, blocks and CSS classes (leave empty for the built-in defaults), plus the custom taxonomies added to the front matter and the ACF fields."},{"src":"https:\/\/ps.w.org\/system-markdown-alternate\/assets\/screenshot-3.png?rev=3639207","caption":"Settings \u2014 llms.txt: enable the endpoint, the enriched output and the last modified date on each entry, then add the site summary and the curated key content."},{"src":"https:\/\/ps.w.org\/system-markdown-alternate\/assets\/screenshot-4.png?rev=3639207","caption":"Settings \u2014 Integrations: the <code>[sysmda_md_url]<\/code> and <code>[sysmda_md_download]<\/code> shortcodes, with the GenerateBlocks and ACF detection status."},{"src":"https:\/\/ps.w.org\/system-markdown-alternate\/assets\/screenshot-5.png?rev=3639207","caption":"Settings \u2014 Advanced: the <code>X-Robots-Tag<\/code> header, the opt-in LiteSpeed cache bypass rules and the <code>.md<\/code> hit counter, split bot vs human."}],"raw_content":"<!--section=description-->\n<p>System Markdown Alternate publishes a clean, machine-readable Markdown\nrepresentation of your content. Append <code>.md<\/code> to any supported permalink and you\nget YAML front matter plus the post body converted to Markdown \u2014 with marketing\nclutter, forms and navigation widgets stripped out.<\/p>\n\n<pre><code>https:\/\/example.com\/my-post\/    \u2192 HTML\nhttps:\/\/example.com\/my-post.md  \u2192 Markdown (front matter + content)\n<\/code><\/pre>\n\n<p>It is built for the era of AI assistants, agents and technical scrapers that\nprefer plain Markdown over rendered HTML. It is <strong>not<\/strong> a generic SEO plugin.<\/p>\n\n<h4>Key features<\/h4>\n\n<ul>\n<li><strong><code>.md<\/code> endpoint<\/strong> for every supported, published, public post.<\/li>\n<li><strong>Content negotiation<\/strong>: the same Markdown is returned for <code>Accept: text\/markdown<\/code>\nor <code>?format=markdown<\/code> requests. The <code>Accept<\/code> header is parsed with q-values, so\na client that prefers HTML (higher q) still gets HTML.<\/li>\n<li><strong><code>Vary: Accept<\/code><\/strong> on negotiable URLs, so caches and CDNs that honour it keep the\nHTML and Markdown representations of the same address apart. Because some page\ncaches key by URL only and ignore <code>Vary<\/code>, the negotiated Markdown (and <code>406<\/code>)\nresponses are also sent non-cacheable, so safety never depends on <code>Vary<\/code> alone.<\/li>\n<li><strong>Markdown discovery in HTML and HTTP<\/strong>: supported canonical pages advertise\nthe representation with both <code>&lt;link rel=\"alternate\" type=\"text\/markdown\"&gt;<\/code>\nin the document head and a typed <code>Link: rel=\"alternate\"<\/code> response header.\nThe HTTP form is also available to <code>HEAD<\/code> requests.<\/li>\n<li><strong>Correct HTTP headers<\/strong>: <code>Content-Type: text\/markdown<\/code>, <code>X-Robots-Tag<\/code>\n(default <code>noindex, follow<\/code>) and a <code>Link: rel=\"canonical\"<\/code> back to the HTML.<\/li>\n<li><strong>Clean conversion<\/strong>: Gutenberg blocks are rendered individually (no injected\nrelated\/CTA blocks), excluded blocks\/shortcodes\/CSS classes are removed, code\nblocks become fenced blocks, URLs are made absolute.<\/li>\n<li><strong><code>\/llms.txt<\/code> endpoint<\/strong> (optional): an index of your content for LLMs and AI\nagents. An optional <strong>enriched mode<\/strong> (off by default) adds a site summary, a\ncurated \"Key content\" section, a description for each entry and an <code>Optional<\/code>\nsection for older posts. Another optional toggle appends the <strong>last modified\ndate<\/strong> (<code>updated: YYYY-MM-DD<\/code>) to every entry, so crawlers can spot changed\ncontent without re-fetching each URL.<\/li>\n<li><strong>Custom taxonomies in the front matter<\/strong> (optional, nothing selected by\ndefault): tick the taxonomies you want and their terms are added as a\n  taxonomies: block, alphabetically ordered. Nothing is ever published\nautomatically: a taxonomy registered by another plugin appears in the panel\nunticked, and taxonomies with no public term archive are labelled as internal.<\/li>\n<li><strong>Object cache<\/strong> with proactive invalidation on post edit, plugin update and\nsettings change: a persistent object cache is used when one is available,\nfalling back to transients otherwise.<\/li>\n<li><strong>Optional <code>.md<\/code> hit counter<\/strong> (off by default): counts how many times the\nMarkdown endpoint is served, split bot vs human. Privacy by design: only\naggregate daily totals are stored \u2014 no IP addresses, no user-agent strings,\nno per-visitor data, no cookies, no external calls.<\/li>\n<li><strong>Admin panel<\/strong> to choose which post types are exposed and to tune cache,\nexclusions and headers \u2014 no post type is exposed until you pick one.<\/li>\n<li><strong>Shortcodes<\/strong> <code>[sysmda_md_url]<\/code> (the Markdown URL) and\n  [sysmda_md_download] (a link that saves the file instead of opening it).<\/li>\n<li><strong>Optional integrations<\/strong>, shown only when the related plugin is active:\n\n<ul>\n<li><strong>Advanced Custom Fields<\/strong>: add a subtitle and a TL;DR (from ACF fields) as a\npreamble between the H1 and the body.<\/li>\n<li><strong>GenerateBlocks 2.x<\/strong>: a <code>{{sysmda_md_url}}<\/code> Dynamic Tag, available\nautomatically, usable in element fields (e.g. a Button URL).<\/li>\n<\/ul><\/li>\n<li><strong>Developer-extensible<\/strong>: every behaviour above \u2014 which content is served, the\nheaders, the caching, the conversion pipeline, the front matter and\n  \/llms.txt \u2014 is exposed as a WordPress filter. See the FAQ below for\nexamples and a link to the full documented list.<\/li>\n<\/ul>\n\n<!--section=installation-->\n<ol>\n<li>Upload the plugin to <code>\/wp-content\/plugins\/<\/code> or install it through the\nPlugins screen in WordPress.<\/li>\n<li>Activate the plugin.<\/li>\n<li>Go to <strong>Settings \u2192 Markdown Alternate<\/strong> and select at least one post type\nunder <strong>Supported post types<\/strong>. Until you do, the plugin stays inactive.<\/li>\n<li>Visit any supported post and append <code>.md<\/code> to its URL.<\/li>\n<\/ol>\n\n<p>No rewrite rules are added, so no permalink flush is required.<\/p>\n\n<!--section=faq-->\n<dl>\n<dt id=\"why%20is%20nothing%20served%20at%20the%20.md%20url%3F\"><h3>Why is nothing served at the .md URL?<\/h3><\/dt>\n<dd><p>By default no post type is enabled. Open <strong>Settings \u2192 Markdown Alternate<\/strong> and\ntick at least one post type under <strong>Supported post types<\/strong>.<\/p><\/dd>\n<dt id=\"which%20content%20does%20not%20get%20a%20.md%20version%3F\"><h3>Which content does NOT get a .md version?<\/h3><\/dt>\n<dd><p>Anything the endpoint would not be able to serve honestly:<\/p>\n\n<ul>\n<li>content types not enabled in the settings page;<\/li>\n<li>drafts, pending and private content, and password-protected posts;<\/li>\n<li>media attachments (always excluded);<\/li>\n<li>posts with a <strong>non-standard post format<\/strong> \u2014 aside, status, quote, link,\ngallery, image, video, audio, chat. These are short snippets, usually\nuntitled, with no editorial body worth serving as a document. Use the\n  sysmda_markdown_excluded_post_formats filter to change that.<\/li>\n<\/ul>\n\n<p>Markdown is also never served for URL <em>variants<\/em> of a post \u2014 its feed, its\noEmbed view, its trackback endpoint, paged comments and the sub-pages of a\npost split with <code>&lt;!--nextpage--&gt;<\/code> \u2014 even with <code>Accept: text\/markdown<\/code>. Only the\ncanonical permalink and its <code>.md<\/code> URL return Markdown.<\/p><\/dd>\n<dt id=\"what%20does%20the%20markdown%20output%20look%20like%3F\"><h3>What does the Markdown output look like?<\/h3><\/dt>\n<dd><p>Each <code>.md<\/code> response is a UTF-8 document with a YAML front-matter block (title,\nURL, Markdown URL, published\/modified dates, and \u2014 when available \u2014 author,\nfeatured image, categories, tags and a description), followed by the <code># Title<\/code>\nheading and the post body converted to clean Markdown. The exact keys, their\norder and the escaping rules are documented as a stable contract, with\nconformance tests, in <code>docs\/output-format.md<\/code> in the source repository.<\/p><\/dd>\n<dt id=\"can%20i%20include%20my%20custom%20taxonomies%3F\"><h3>Can I include my custom taxonomies?<\/h3><\/dt>\n<dd><p>Yes. Open <strong>Settings \u2192 Markdown Alternate \u2192 Markdown output<\/strong> and tick the ones\nyou want under <em>Custom taxonomies<\/em>: the front matter then carries a\n    taxonomies: block with their terms, sorted alphabetically. Categories and tags\nalready have their own keys and are not repeated.<\/p>\n\n<p>Nothing is selected by default and nothing is ever added implicitly \u2014 a taxonomy\nregistered by a plugin you install later shows up in the list unticked, so it\ncannot start publishing itself. Taxonomies used for editorial classification\nonly, with no public term archive (\"publicly queryable\" off), are labelled as\ninternal in the list: they are still selectable, but only on purpose. Developers\ncan curate the list further with the <code>sysmda_front_matter_taxonomy_slugs<\/code> filter.<\/p><\/dd>\n<dt id=\"how%20do%20i%20exclude%20part%20of%20a%20post%20from%20the%20markdown%3F\"><h3>How do I exclude part of a post from the Markdown?<\/h3><\/dt>\n<dd><p>Add one of the CSS classes <code>no-md<\/code>, <code>md-exclude<\/code> or <code>exclude-from-markdown<\/code> to a\nblock; the element (and its children) is removed from the Markdown output. You\ncan customize the list with the <code>sysmda_markdown_excluded_classes<\/code> filter.<\/p><\/dd>\n<dt id=\"does%20it%20affect%20my%20seo%3F\"><h3>Does it affect my SEO?<\/h3><\/dt>\n<dd><p>The <code>.md<\/code> responses are sent with <code>X-Robots-Tag: noindex, follow<\/code> and a\n    Link: rel=\"canonical\" header pointing back to the HTML version, so search\nengines are told to prefer the original page.<\/p><\/dd>\n<dt id=\"how%20do%20i%20get%20the%20markdown%20url%20in%20a%20button%20or%20template%3F\"><h3>How do I get the Markdown URL in a button or template?<\/h3><\/dt>\n<dd><p>Use the <code>[sysmda_md_url]<\/code> shortcode. If you run GenerateBlocks 2.x, the\n    {{sysmda_md_url}} Dynamic Tag is available automatically \u2014 use it in element\nfields such as a Button URL. When the post has no <code>.md<\/code>, the tag resolves to an\nempty value so GenerateBlocks can hide the element instead of leaving a broken\nlink.<\/p><\/dd>\n<dt id=\"how%20do%20i%20let%20readers%20download%20the%20.md%20instead%20of%20opening%20it%3F\"><h3>How do I let readers download the .md instead of opening it?<\/h3><\/dt>\n<dd><p>Use the <code>[sysmda_md_download]<\/code> shortcode. It prints a link that saves the file:<\/p>\n\n<pre><code>[sysmda_md_download]\n[sysmda_md_download text=\"Save the Markdown\"]\n[sysmda_md_download id=\"123\"]\n<\/code><\/pre>\n\n<p>The link carries the HTML <code>download<\/code> attribute, which is what tells the browser\nto save the file instead of displaying it. The file name comes from the post\nslug. Nothing changes on the server side: the <code>.md<\/code> URL itself behaves exactly\nas it always has, so opening it directly still shows whatever your browser\nnormally does with a Markdown file.<\/p>\n\n<p>The shortcode outputs a plain link with a single <code>sysmda-md-download<\/code> class, and\nthe plugin loads <strong>no CSS and no JavaScript<\/strong> on your site for it. Any styling is\nyour theme's job.<\/p>\n\n<p>Like <code>[sysmda_md_url]<\/code>, it outputs nothing when the post has no Markdown version,\nso it can never produce a link to a 404.<\/p><\/dd>\n<dt id=\"is%20the%20.md%20content%20cached%3F\"><h3>Is the .md content cached?<\/h3><\/dt>\n<dd><p>Yes (default 24h). It uses a persistent object cache when one is available and\nfalls back to transients otherwise. The cache is regenerated automatically when\nthe post is edited, when the plugin is updated, or when you save the settings \u2014\nand also when something outside the post changes what the Markdown says: a\nsynced pattern, the featured image, the description, an ACF field, the author's\ndisplay name, the permalink structure or the site address.<\/p>\n\n<p>That is the cache inside WordPress. Caches <em>outside<\/em> it \u2014 your browser, a page\ncache, Varnish, a CDN \u2014 are told\n    Cache-Control: public, max-age=0, must-revalidate: they may keep a copy, but\nthey must ask the site whether it is still current before serving it, and the\nanswer is a small <code>304 Not Modified<\/code> when nothing changed. So a <code>.md<\/code> cannot\nkeep circulating after you edit the article, without depending on anyone\npurging it \u2014 which matters, because page caches purge the article's URL and do\nnot know its <code>.md<\/code> version exists. If your infrastructure has its own purge\nmechanism and you would rather trade that guarantee for raw speed, the\n    sysmda_cache_control filter lets you set a real lifetime.<\/p><\/dd>\n<dt id=\"can%20i%20customize%20the%20plugin%20from%20my%20own%20code%3F\"><h3>Can I customize the plugin from my own code?<\/h3><\/dt>\n<dd><p>Yes: the plugin is developer-extensible through WordPress filters \u2014 which\ncontent is served, the HTTP headers, the caching, every stage of the conversion\npipeline, the front matter and <code>\/llms.txt<\/code> can all be changed from a theme or a\nsite plugin. A few examples:<\/p>\n\n<pre><code>add_filter( 'sysmda_markdown_output', fn( $md, $post ) =&gt; $md . \"\\n---\\nCustom footer.\\n\", 10, 2 );\n\nadd_filter( 'sysmda_markdown_excluded_classes', fn( $classes ) =&gt; array_merge( $classes, array( 'my-private-block' ) ) );\n\nadd_filter( 'sysmda_llms_txt_enriched', '__return_true' );\n<\/code><\/pre>\n\n<p>Every filter, with its default value and what changing it does, is documented\nhere: <a href=\"https:\/\/github.com\/diecieventi\/system-markdown-alternate\/blob\/main\/docs\/filters.md\">Filters (public contract)<\/a>.<\/p><\/dd>\n<dt id=\"content%20negotiation%20misbehaves%20behind%20litespeed%20cache.%20what%20can%20i%20do%3F\"><h3>Content negotiation misbehaves behind LiteSpeed cache. What can I do?<\/h3><\/dt>\n<dd><p>Some LiteSpeed cache configurations key the page cache by URL only and ignore\n    Vary: Accept, so a cached representation can be served regardless of the\n    Accept header. The plugin already tells the cache not to store the negotiated\nMarkdown; if requests for Markdown on the permalink still receive cached HTML,\nenable <strong>LiteSpeed cache compatibility<\/strong> in <strong>Settings \u2192 Markdown Alternate \u2192\nAdvanced<\/strong>: it adds <code>.htaccess<\/code> rules that make Markdown-negotiating requests\nbypass the LiteSpeed page cache (normal browser traffic stays cached; on other\nservers the rules are inert). Then purge the LiteSpeed cache. The explicit\n    .md URLs are not affected and remain fully cacheable.<\/p>\n\n<p>Not sure whether your host is affected? Whether a LiteSpeed server honours\n    Vary: Accept depends on the host and cannot be detected automatically, so if\nin doubt simply enable the option: it is the safe choice, and on hosts that\nalready behave correctly the rules are just redundant. To test it yourself:\nopen a post in a normal browser first (so its HTML gets cached), then request\nthe same permalink with a Markdown Accept header, for example:<\/p>\n\n<pre><code>curl -A \"Mozilla\/5.0\" -H \"Accept: text\/markdown\" https:\/\/example.com\/my-post\/\n<\/code><\/pre>\n\n<p>If the response is HTML (often with an <code>x-litespeed-cache: hit<\/code> header) instead\nof Markdown, your server ignores <code>Vary: Accept<\/code> and you need the option. The\nbrowser-like <code>-A<\/code> value matters: a WAF\/CDN may block non-browser user agents.<\/p><\/dd>\n<dt id=\"does%20it%20work%20behind%20a%20cdn%20%28cloudflare%2C%20fastly%2C%20varnish%29%3F\"><h3>Does it work behind a CDN (Cloudflare, Fastly, Varnish)?<\/h3><\/dt>\n<dd><p>The <code>.md<\/code> URLs need nothing from you. The negotiated permalink depends on your\nCDN, and the difference matters:<\/p>\n\n<ul>\n<li>the dedicated <code>.md<\/code> URLs are their own cache key \u2014 one URL, one\nrepresentation, nothing to mix up. Any CDN may store them, and\n  Cache-Control: public, max-age=0, must-revalidate means it must revalidate\nbefore reuse, which is a cheap <code>304 Not Modified<\/code> when nothing changed. This\nroute works everywhere, with no configuration;<\/li>\n<li>the <strong>negotiated<\/strong> permalink (<code>Accept: text\/markdown<\/code> on the HTML page's own\nURL) is sent <code>no-store<\/code>, so a Markdown response is never stored and can never\nbe handed to a browser that asked for HTML. That closes the harmful direction,\nbut it cannot fix the opposite one: if your CDN caches the HTML page by URL and\nignores <code>Vary: Accept<\/code>, a later Markdown request is answered at the edge, PHP\nnever runs, and the client simply gets HTML. <code>Vary: Accept<\/code> is sent on every\nnegotiable response, which is all a cache that honours it needs.<\/li>\n<\/ul>\n\n<p>So for the negotiated route one of these has to be true: your CDN honours\n    Vary: Accept, or you configure it to bypass the cache \u2014 or to vary its cache\nkey \u2014 for requests whose <code>Accept<\/code> mentions <code>text\/markdown<\/code>. On LiteSpeed the\nplugin ships that bypass for you: see the previous entry. If you are not sure\nwhich case you are in, the three-request test below tells you in a few seconds.\nAnd the <code>.md<\/code> URL keeps working regardless \u2014 it is what the <code>rel=\"alternate\"<\/code>\nlink and <code>\/llms.txt<\/code> advertise, so agents following either one are unaffected.<\/p>\n\n<p>Two more things worth knowing. Some CDNs rewrite validators in transit \u2014 Cloudflare\nturns a strong <code>ETag<\/code> into a weak one \u2014 which the plugin handles: incoming\nvalidators are compared with the weak-comparison rules, so revalidation keeps\nworking either way. And if you would rather have the CDN really cache the <code>.md<\/code>\ninstead of revalidating it, set a lifetime with the <code>sysmda_cache_control<\/code>\nfilter, keeping in mind that nothing purges a <code>.md<\/code> when you edit the post.<\/p><\/dd>\n<dt id=\"how%20do%20i%20check%20my%20cache%20is%20not%20mixing%20html%20and%20markdown%3F\"><h3>How do I check my cache is not mixing HTML and Markdown?<\/h3><\/dt>\n<dd><p>Send three requests to the same permalink, in this order, and compare the\n    content-type of each:<\/p>\n\n<pre><code>curl -sI -A \"Mozilla\/5.0\" -H \"Accept: text\/markdown\" https:\/\/example.com\/my-post\/\ncurl -sI -A \"Mozilla\/5.0\" -H \"Accept: text\/html\" https:\/\/example.com\/my-post\/\ncurl -sI -A \"Mozilla\/5.0\" -H \"Accept: text\/markdown\" https:\/\/example.com\/my-post\/\n<\/code><\/pre>\n\n<p>The first and third must answer <code>text\/markdown<\/code>, the second <code>text\/html<\/code>. If the\nsecond returns Markdown, or the third returns HTML, something in front of PHP is\nserving one stored representation to everyone: look at the <code>age<\/code>, <code>x-cache<\/code>,\n    cf-cache-status or <code>x-litespeed-cache<\/code> headers to see which layer, and purge it\n(on LiteSpeed, see the entry above).<\/p>\n\n<p>To check revalidation on a <code>.md<\/code> URL, read its <code>etag<\/code> and send it back:<\/p>\n\n<pre><code>curl -sI -A \"Mozilla\/5.0\" https:\/\/example.com\/my-post.md\ncurl -sI -A \"Mozilla\/5.0\" -H 'If-None-Match: W\/\"paste-the-etag-here\"' https:\/\/example.com\/my-post.md\n<\/code><\/pre>\n\n<p>The second request should answer <code>304<\/code> with no body. A <code>200<\/code> instead is usually\nnot the plugin: some stacks strip conditional headers from the request before PHP\never sees them (observed with nginx configured to cache the location). It is a\nmissed optimisation, not a correctness problem \u2014 the response is still current.<\/p>\n\n<p>As above, the browser-like <code>-A<\/code> value matters: a WAF\/CDN may block non-browser\nuser agents outright, and a block page is easy to mistake for a plugin bug.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>0.37.0<\/h4>\n\n<ul>\n<li>Supported canonical HTML pages now advertise their Markdown representation in\nthe HTTP <code>Link<\/code> header as well as in the document <code>&lt;head&gt;<\/code>. The header is also\npresent on <code>HEAD<\/code> responses, appends without replacing other link relations\nand is not emitted on <code>.md<\/code>, negotiated Markdown, <code>406<\/code> or redirect responses.<\/li>\n<li>Simplified release packaging around one shared <code>.distignore<\/code>: the local build\nand the wordpress.org deploy now stage the same files, and the obsolete\n  BUILD-INFO.txt artifact is gone.<\/li>\n<li>Cleaned up the test bootstrap for PHP 8.5 and removed an empty duplicate\n  php_codesniffer test suite from CI.<\/li>\n<\/ul>\n\n<h4>0.36.0<\/h4>\n\n<ul>\n<li><strong>Renaming a category or tag now refreshes the Markdown.<\/strong> <code>categories:<\/code> and\n  tags: are always part of the front matter, but nothing told the caching\nlayer they had changed, so a client that had already fetched a post kept being\ntold \"not modified\" \u2014 indefinitely, with the cache on or off. Changing the\nsite timezone had the same effect on the dates, and replacing the file behind a\nfeatured image on its URL.<\/li>\n<li><strong>Fenced code inside a quote or a list item is preserved again.<\/strong> Only code at\nthe left margin was recognised as code, so anything indented inside a\nblockquote or a list had its trailing spaces trimmed and its blank lines\ncollapsed \u2014 silently rewriting samples, transcripts and diffs.<\/li>\n<li><strong><code>Vary: Accept<\/code> is no longer skipped by mistake.<\/strong> A site already sending\n  Vary: Accept-Encoding (most of them, once compression is on) looked to the\nplugin as if the header were covered, and it was never added \u2014 leaving caches\nfree to hand the HTML page to a client asking for Markdown.<\/li>\n<li><strong>The <code>.md<\/code> is now explicitly the anonymous version of a post.<\/strong> A logged-in\nvisitor's request is never stored in the shared cache and is never publicly\ncacheable, so a block or shortcode that renders differently for that visitor\ncannot end up being served to everyone else.<\/li>\n<li><strong>New <code>sysmda_post_is_servable<\/code> filter<\/strong> so a membership or paywall plugin can\ndeny the Markdown of a single post. The built-in checks only understand\nWordPress's own post status and password field.<\/li>\n<li>A post type that is no longer registered as public stops being served, instead\nof remaining servable because its name was still saved in the settings.<\/li>\n<li><code>?format=banana<\/code> no longer disables the <code>406<\/code> response that <code>?format=markdown<\/code>\nis allowed to skip.<\/li>\n<li>A read error while updating <code>.htaccess<\/code> now aborts the update instead of\nrewriting the file from the part that had been read.<\/li>\n<li><code>\/llms.txt<\/code> counts eligible posts against its per-type limit, so a batch of\nexcluded ones no longer shortens the index \u2014 or empties a section that still\nhas content behind it.<\/li>\n<li>The panel now distinguishes \"<code>\/llms.txt<\/code> enabled\" from \"enabled but waiting for\na content type\", which is when the endpoint deliberately stays silent.<\/li>\n<li>Control characters arriving from an import or a REST write can no longer break\nthe YAML front matter.<\/li>\n<li>Hardened the wordpress.org release workflow: every GitHub Action is pinned to\nan exact revision, and a deploy is refused unless the tag exists and the\nversion agrees across the plugin header, the readme and the changelog.<\/li>\n<\/ul>\n\n<h4>0.35.4<\/h4>\n\n<ul>\n<li>Moved the developer filter reference into a dedicated, better organized page\n(linked from the FAQ below and from the GitHub repository), out of the\ncontributor guide it used to share. No behaviour changed.<\/li>\n<\/ul>\n\n<p><a href=\"https:\/\/github.com\/diecieventi\/system-markdown-alternate\/blob\/main\/CHANGELOG.md\">View the full changelog<\/a><\/p>","raw_excerpt":"Exposes a clean Markdown version of your posts (readable by LLMs, agents and technical tools) by appending .md to the permalink.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/338045","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=338045"}],"author":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/system4pc"}],"wp:attachment":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=338045"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=338045"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=338045"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=338045"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=338045"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=338045"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}