{"id":306639,"date":"2026-05-07T09:21:08","date_gmt":"2026-05-07T09:21:08","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/markdown-negotiator\/"},"modified":"2026-05-07T09:21:33","modified_gmt":"2026-05-07T09:21:33","slug":"markdown-negotiator","status":"publish","type":"plugin","link":"https:\/\/wordpress.org\/plugins\/markdown-negotiator\/","author":23194996,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.1.1","stable_tag":"1.1.1","tested":"6.9.4","requires":"6.0","requires_php":"7.4","requires_plugins":null,"header_name":"Umay AI Markdown","header_author":"Umay Ajans","header_description":"Serves Markdown content to AI agents and crawlers that request `Accept: text\/markdown`. Falls back to normal HTML for browsers. Zero-config, transient-cached, PSR-4 OOP.","assets_banners_color":"090c2a","last_updated":"2026-05-07 09:21:33","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/www.umayajans.com\/","header_author_uri":"https:\/\/umayajans.com\/","rating":0,"author_block_rating":0,"active_installs":0,"downloads":31,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.1.1":{"tag":"1.1.1","author":"umayajans","date":"2026-05-07 09:21:33"}},"upgrade_notice":{"1.1.1":"<p>Aligned text domain with plugin slug (<code>markdown-negotiator<\/code>), hardened Markdown output escaping for Plugin Check compliance, and switched distribution language files to English POT-only.<\/p>","1.1.0":"<p>Plugin renamed to &quot;Umay AI Markdown&quot; (slug <code>markdown-negotiator<\/code>). Removes deprecated libxml calls and refactors the output buffer pipeline for WordPress.org compliance. After upgrading, deactivate and re-activate to flush the cache.<\/p>","1.0.1":"<p>Cleaner Markdown output: strips inline page-builder CSS, fixes lazy-loaded images, removes JavaScript pop-up links, and adds translation support.<\/p>","1.0.0":"<p>First public release.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3525268,"resolution":"128x128","location":"assets","locale":""},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3525268,"resolution":"256x256","location":"assets","locale":""}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3525268,"resolution":"1544x500","location":"assets","locale":""},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3525268,"resolution":"772x250","location":"assets","locale":""}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.1.1"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3525268,"resolution":"1","location":"assets","locale":""},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3525268,"resolution":"2","location":"assets","locale":""},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3525268,"resolution":"3","location":"assets","locale":""}},"screenshots":{"1":"Markdown response served when an AI agent requests <code>Accept: text\/markdown<\/code> \u2014 full HTTP headers (Content-Type, Vary, X-Cache, X-Robots-Tag, X-Content-Type-Options, X-Markdown-Generator), followed by a YAML frontmatter block (title, site, URL, generated timestamp) and the converted Markdown body.","2":"Built-in transient cache in action: the first request to a URL returns <code>X-Cache: MISS<\/code> (Markdown generated on the fly), the next request to the same URL returns <code>X-Cache: HIT<\/code> (served from cache, no rendering work).","3":"Transparent content negotiation: same WordPress URL, two different responses based on the <code>Accept<\/code> request header \u2014 browsers receive <code>text\/html<\/code> (the normal theme output), AI agents receive <code>text\/markdown<\/code> (clean, machine-readable content) with the plugin's enriched header set."},"jetpack_post_was_ever_published":false},"plugin_section":[],"plugin_tags":[246305,257149,2591,226124,4608],"plugin_category":[49],"plugin_contributors":[262143],"plugin_business_model":[],"class_list":["post-306639","plugin","type-plugin","status-publish","hentry","plugin_tags-ai-agents","plugin_tags-content-negotiation","plugin_tags-geo","plugin_tags-llm","plugin_tags-markdown","plugin_category-maps-and-location","plugin_contributors-umayajans","plugin_committers-umayajans"],"banners":{"banner":"https:\/\/ps.w.org\/markdown-negotiator\/assets\/banner-772x250.png?rev=3525268","banner_2x":"https:\/\/ps.w.org\/markdown-negotiator\/assets\/banner-1544x500.png?rev=3525268","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/markdown-negotiator\/assets\/icon-128x128.png?rev=3525268","icon_2x":"https:\/\/ps.w.org\/markdown-negotiator\/assets\/icon-256x256.png?rev=3525268","generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/markdown-negotiator\/assets\/screenshot-1.png?rev=3525268","caption":"Markdown response served when an AI agent requests <code>Accept: text\/markdown<\/code> \u2014 full HTTP headers (Content-Type, Vary, X-Cache, X-Robots-Tag, X-Content-Type-Options, X-Markdown-Generator), followed by a YAML frontmatter block (title, site, URL, generated timestamp) and the converted Markdown body."},{"src":"https:\/\/ps.w.org\/markdown-negotiator\/assets\/screenshot-2.png?rev=3525268","caption":"Built-in transient cache in action: the first request to a URL returns <code>X-Cache: MISS<\/code> (Markdown generated on the fly), the next request to the same URL returns <code>X-Cache: HIT<\/code> (served from cache, no rendering work)."},{"src":"https:\/\/ps.w.org\/markdown-negotiator\/assets\/screenshot-3.png?rev=3525268","caption":"Transparent content negotiation: same WordPress URL, two different responses based on the <code>Accept<\/code> request header \u2014 browsers receive <code>text\/html<\/code> (the normal theme output), AI agents receive <code>text\/markdown<\/code> (clean, machine-readable content) with the plugin's enriched header set."}],"raw_content":"<!--section=description-->\n<p>Modern AI agents (ChatGPT, Claude, Perplexity, Gemini, etc.) work much better with Markdown than HTML. Umay AI Markdown inspects the incoming <code>Accept<\/code> header and, only when <code>text\/markdown<\/code> is requested, intercepts the response and serves a clean, agent-friendly Markdown representation of the page.<\/p>\n\n<p>Browsers, search engines, and any client that does not explicitly ask for Markdown receive the unchanged HTML response. There is no settings page, no cron job, and no external service call.<\/p>\n\n<p><strong>Key features<\/strong><\/p>\n\n<ul>\n<li>Zero configuration \u2014 install, activate, done.<\/li>\n<li>Only triggers when <code>Accept: text\/markdown<\/code> is present. Regular visitors and search engines are never affected.<\/li>\n<li>Hybrid content extraction: uses <code>the_content<\/code> for posts\/pages, falls back to a DOM-based extractor for archives, taxonomies, and the homepage.<\/li>\n<li>Powered by the industry-standard <a href=\"https:\/\/github.com\/thephpleague\/html-to-markdown\">league\/html-to-markdown<\/a> library.<\/li>\n<li>Transient-cached for 12 hours per URL (sha256-keyed). Auto-invalidated on <code>save_post<\/code>, term edits, theme switches, and menu updates.<\/li>\n<li>Built-in IP rate limiter (30 requests \/ minute by default) to mitigate abuse.<\/li>\n<li>Strict input sanitization, header injection protection, libxml entity hardening (XXE-safe), and full WordPress Coding Standards compliance.<\/li>\n<li>PSR-4 autoloaded, namespaced OOP code. No globals.<\/li>\n<li>Sends <code>Vary: Accept<\/code>, <code>X-Robots-Tag: noindex<\/code>, and <code>X-Content-Type-Options: nosniff<\/code> on every Markdown response.<\/li>\n<\/ul>\n\n<p><strong>What gets sent to AI agents<\/strong><\/p>\n\n<p>Each Markdown response includes a YAML front-matter block with the page title, site name, canonical URL, and ISO-8601 generation timestamp, followed by the page body converted to Markdown. Navigation, footer, sidebars, scripts, styles, comment forms, related posts, and other page chrome are stripped before conversion.<\/p>\n\n<p><strong>Filters<\/strong><\/p>\n\n<p>Two filters are available for advanced customization:<\/p>\n\n<ul>\n<li><code>umay_mdn_bypass<\/code> \u2014 Return <code>true<\/code> to skip Markdown handling for the current request.<\/li>\n<li><code>umay_mdn_cache_ttl<\/code> \u2014 Override the default 12-hour cache lifetime (in seconds, minimum 60).<\/li>\n<li><code>umay_mdn_rate_limit<\/code> \u2014 Override the default 30-requests-per-minute rate limit.<\/li>\n<li><code>umay_mdn_converter_options<\/code> \u2014 Modify the league\/html-to-markdown converter options array.<\/li>\n<\/ul>\n\n<!--section=installation-->\n<ol>\n<li>Upload the <code>markdown-negotiator<\/code> folder (or the ZIP) via <strong>Plugins &gt; Add New &gt; Upload Plugin<\/strong>.<\/li>\n<li>Activate the plugin.<\/li>\n<li>There is no settings page. The plugin starts working immediately.<\/li>\n<\/ol>\n\n<p>To verify:<\/p>\n\n<pre><code>curl -H \"Accept: text\/markdown\" https:\/\/your-site.com\/\n<\/code><\/pre>\n\n<p>You should get back a <code>Content-Type: text\/markdown; charset=utf-8<\/code> response with a YAML front-matter and a Markdown body.<\/p>\n\n<!--section=faq-->\n<dl>\n<dt id=\"does%20it%20slow%20down%20my%20normal%20site%3F\"><h3>Does it slow down my normal site?<\/h3><\/dt>\n<dd><p>No. The plugin returns immediately on <code>template_redirect<\/code> priority 1 if the request does not include <code>Accept: text\/markdown<\/code>. The cost is roughly a single <code>if<\/code> check per request.<\/p><\/dd>\n<dt id=\"does%20this%20plugin%20contact%20any%20external%20services%3F\"><h3>Does this plugin contact any external services?<\/h3><\/dt>\n<dd><p>No. The plugin does not call any external service, does not send analytics, does not check for updates against a remote server, and does not load any remote assets. All HTML-to-Markdown conversion happens locally using the bundled <code>league\/html-to-markdown<\/code> library.<\/p><\/dd>\n<dt id=\"how%20is%20the%20cache%20invalidated%3F\"><h3>How is the cache invalidated?<\/h3><\/dt>\n<dd><p>Per-URL cache keys are deleted on <code>save_post<\/code> and <code>transition_post_status<\/code> for the affected post. Term edits, theme switches, and menu updates flush the entire Markdown cache.<\/p><\/dd>\n<dt id=\"is%20logged-in%20personalization%20supported%3F\"><h3>Is logged-in personalization supported?<\/h3><\/dt>\n<dd><p>No. Logged-in requests always fall back to HTML to avoid leaking nonces, admin bars, or per-user content into a shared Markdown cache. This is by design.<\/p><\/dd>\n<dt id=\"how%20do%20i%20clear%20the%20cache%20manually%3F\"><h3>How do I clear the cache manually?<\/h3><\/dt>\n<dd><p>Deactivate and re-activate the plugin. Deactivation calls <code>Cache::flush_all()<\/code>.<\/p><\/dd>\n<dt id=\"is%20the%20markdown%20response%20indexable%20by%20search%20engines%3F\"><h3>Is the Markdown response indexable by search engines?<\/h3><\/dt>\n<dd><p>No. Every Markdown response includes <code>X-Robots-Tag: noindex<\/code> to prevent duplicate-content issues. The HTML version remains the canonical, indexable representation.<\/p><\/dd>\n<dt id=\"does%20this%20work%20with%20caching%20plugins%20like%20litespeed%20cache%2C%20wp%20rocket%2C%20or%20w3%20total%20cache%3F\"><h3>Does this work with caching plugins like LiteSpeed Cache, WP Rocket, or W3 Total Cache?<\/h3><\/dt>\n<dd><p>The plugin sets <code>Vary: Accept<\/code> so any well-behaved cache layer will store the Markdown and HTML variants separately. If your cache layer ignores the Vary header, exclude the URLs from the cache when the <code>Accept: text\/markdown<\/code> header is present.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.1.1<\/h4>\n\n<ul>\n<li>Aligned text domain with WordPress.org plugin slug: changed from <code>umay-ai-markdown<\/code> to <code>markdown-negotiator<\/code> across plugin header, gettext calls, and POT file (per WP.org review feedback).<\/li>\n<li>Hardened Markdown response output: replaced raw <code>echo $markdown<\/code> with <code>wp_kses( $markdown, array() )<\/code> to satisfy Plugin Check's late-escape rule while preserving Markdown syntax.<\/li>\n<li>Removed bundled tr_TR translation files; only the English POT template ships in the WordPress.org distribution. Translations now flow through translate.wordpress.org.<\/li>\n<\/ul>\n\n<h4>1.1.0<\/h4>\n\n<ul>\n<li>Renamed plugin: \"Markdown Negotiator\" \u2192 \"Umay AI Markdown\"; slug and text domain changed to <code>markdown-negotiator<\/code> (per WordPress.org review feedback to ensure the name is distinctive).<\/li>\n<li>Removed deprecated <code>libxml_disable_entity_loader()<\/code> calls in <code>ContentExtractor<\/code> and <code>Converter<\/code>. XXE protection unchanged: libxml 2.9+ disables external entities by default, and <code>LIBXML_NONET<\/code> is still passed to <code>loadHTML()<\/code>.<\/li>\n<li>Refactored Tier 2 (non-singular) request pipeline: replaced the open <code>ob_start( $callback )<\/code> on <code>template_redirect<\/code> with a <code>template_include<\/code> filter that opens and closes its buffer (<code>ob_start<\/code> paired with <code>ob_get_clean<\/code>) inside a single function scope, satisfying Plugin Check's buffer-pairing requirement.<\/li>\n<li>Added <code>try\/catch<\/code> around the template render so a fatal in a theme\/plugin returns a clean 500 instead of a half-buffered response.<\/li>\n<\/ul>\n\n<h4>1.0.1<\/h4>\n\n<ul>\n<li>HTML sanitization hardening: strip <code>&lt;style&gt;<\/code>, <code>&lt;script&gt;<\/code>, <code>&lt;noscript&gt;<\/code> blocks via regex (defense in depth on top of league\/html-to-markdown's <code>remove_nodes<\/code>).<\/li>\n<li>Lazy-load image normalization: promotes <code>data-lazy-src<\/code> \/ <code>data-src<\/code> \/ <code>data-original<\/code> and the first <code>srcset<\/code> URL into the real <code>src<\/code>. Drops empty\/placeholder images.<\/li>\n<li>Page-builder anchor cleanup: anchors with <code>href=\"#elementor-action:...\"<\/code>, <code>javascript:<\/code>, or bare <code>#<\/code> are unwrapped to plain text.<\/li>\n<li>Internationalization: text domain is now loaded on <code>init<\/code> and a base POT file ships in <code>\/languages\/<\/code>.<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>Initial release.<\/li>\n<\/ul>","raw_excerpt":"Serves clean Markdown to AI agents (ChatGPT, Claude, Perplexity) on Accept: text\/markdown. HTML stays default for browsers. Zero-config.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/306639","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=306639"}],"author":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/umayajans"}],"wp:attachment":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=306639"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=306639"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=306639"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=306639"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=306639"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=306639"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}