| 开发者 | umayajans |
|---|---|
| 更新时间 | 2026年11月1日 02:17 |
| PHP版本: | 7.4 及以上 |
| WordPress版本: | 7.1 |
| 版权: | GPLv2 or later |
| 版权网址: | 版权信息 |
Accept: text/markdown receives Markdown on the same URL./your-post.md returns the Markdown of /your-post/, and /index.md the home page. Every HTML page advertises its twin with <link rel="alternate" type="text/markdown"> and a matching Link header. These URLs also work behind page caches and CDNs..md URL with its excerpt. An optional /llms-full.txt contains the full Markdown of every entry..md URLs and /llms.txt need pretty permalinks (Settings > Permalinks, anything but "Plain").
What agents receive
Each Markdown document starts with YAML front matter (title, description, canonical URL, site, language, post type, author, published and modified dates, featured image, categories and tags), followed by the page content as Markdown. Structured data (JSON-LD) that your SEO plugin prints in the page head (Yoast SEO, Rank Math and others) or that sits in the content is appended as fenced json blocks, the same layout Cloudflare's "Markdown for Agents" uses. Navigation, headers, footers, sidebars, scripts, styles, forms and comment areas are removed; tables become Markdown tables, embedded videos and maps become links, and root-relative links become absolute.
Key features
the_content for posts and pages (works with the block editor, Elementor and other builders), DOM extraction of the rendered theme for archives, the home page and taxonomies.X-Markdown-Tokens (token estimate), Link: rel="canonical", Content-Language, Last-Modified, Vary: Accept, and optional Content-Signal.umay_mdn_bypass (bool, default false) — return true to skip Markdown for the current request.umay_mdn_md_suffix (bool, default true with pretty permalinks) — serve and advertise .md URLs.umay_mdn_llms_txt (bool, default true) — serve /llms.txt.umay_mdn_llms_full (bool, default false) — serve /llms-full.txt.umay_mdn_llms_post_types (string[]) — post types listed in llms.txt.umay_mdn_llms_limit (int, default 100) — entries per post type in llms.txt.umay_mdn_content_signal (string, default empty) — Content-Signal preference, e.g. search=yes, ai-input=yes, ai-train=no.umay_mdn_front_matter (array) — modify the front matter fields.umay_mdn_converter_options (array) — league/html-to-markdown options.umay_mdn_cache_ttl (int, default 43200) — cache lifetime in seconds (minimum 60).umay_mdn_rate_limit (int, default 30) — uncached renders per IP per minute.umay_mdn_vary_html (bool, default true) — send Vary: Accept on HTML responses.umay_mdn_client_ip (string) — client IP used for rate limiting (for sites behind a proxy or CDN).umay_mdn_layout_template (bool, $template) — whether a template that a plugin swapped in only changes the layout (page-builder templates by default), so the post content is still read directly.umay_mdn_llms_include (bool, $post) — return false to leave a post out of llms.txt and llms-full.txt.umay_mdn_ignored_cookies (string[]) — cookie name prefixes that do not personalize pages (analytics, CDN); any other cookie makes a response uncacheable.do_action( 'umay_mdn_flush' ) clears the Markdown cache.
curl -H "Accept: text/markdown" https://your-site.com/
curl https://your-site.com/index.md
curl https://your-site.com/llms.txt
The first two return Content-Type: text/markdown; charset=utf-8 with YAML front matter and a Markdown body.No. For normal visitors the plugin only checks the Accept header and adds two small hints (a <link> tag and a Link header). Markdown is generated only when an agent asks for it, and then cached.
No. It makes no external requests, sends no analytics and loads no remote assets. All conversion happens on your server with the bundled league/html-to-markdown library.
Markdown responses are never stored by page caches: the plugin defines DONOTCACHEPAGE, tells LiteSpeed Cache not to cache the response and sends no-store headers, so browsers never receive Markdown from a cache. The .md URLs and /llms.txt always work, because they are separate addresses.
Content negotiation on the same URL (the Accept header) also requires the cache to tell agents and browsers apart. HTML responses carry Vary: Accept, which caches such as Varnish, nginx and browsers honor. A full-page cache that ignores Vary may hand its cached HTML to an agent that asked for Markdown; the agent still gets correct content, and it can use the advertised .md URL. To negotiate on the same URL behind such a cache, configure it to bypass (or vary on) requests whose Accept header contains text/markdown, for example with a Cloudflare Cache Rule.
If a physical llms.txt file exists in your site root (Yoast SEO's llms.txt feature writes one), the web server serves that file and this plugin's version is not used. To use this plugin's version, turn off the llms.txt feature of the SEO plugin and delete the file. To keep the SEO plugin's file, you can disable ours with add_filter( 'umay_mdn_llms_txt', '__return_false' );.
No duplicate content is created. Negotiated Markdown is served on the page's own URL and carries a Link: rel="canonical" header. The .md URLs additionally send X-Robots-Tag: noindex and point to the HTML page as canonical.
No. Password-protected posts are never served as Markdown, not even to visitors who entered the password, and private, draft, scheduled and trashed content is never served to visitors. Negotiated Markdown is only generated for visitors who are not logged in; logged-in users can preview .md URLs of content they are allowed to see, and those responses are never cached. Responses for visitors carrying cookies (other than common analytics and CDN cookies) are never cached either, so content a membership plugin unlocks by cookie stays with that visitor. llms.txt summaries come only from manual excerpts, so text of restricted posts is not quoted there; posts hidden by your own access rules can be left out with the umay_mdn_llms_include filter.
Yes. Markdown is produced at the very end of WordPress's request handling: plugins that redirect (login walls), set an error status such as 503 (maintenance) or switch to their own template (coming-soon pages) apply first, and the agent receives what a visitor would see.
Add a filter, for example in a small plugin or your theme's functions.php:
add_filter( 'umay_mdn_content_signal', function () { return 'search=yes, ai-input=yes, ai-train=no'; } );
The value is sent as a Content-Signal header with every Markdown response and added under User-agent: * in the robots.txt that WordPress generates. A physical robots.txt file is not changed.
Automatically: the affected URLs (post, home page, archives, author and term pages, llms.txt) when a post changes; everything after a theme switch, a menu or term edit, a plugin/theme/core update, or deactivation. To clear it manually run do_action( 'umay_mdn_flush' );, for example with WP-CLI: wp eval 'do_action( "umay_mdn_flush" );'.
.md URLs (/post.md, /index.md) return Markdown without an Accept header and are advertised on every page with <link rel="alternate" type="text/markdown"> and a Link header./llms.txt with .md links and excerpts; optional /llms-full.txt (filter umay_mdn_llms_full).json blocks.X-Markdown-Tokens, Link: rel="canonical", Content-Language and Last-Modified headers; optional Content-Signal header and robots.txt line (filter umay_mdn_content_signal).DONOTCACHEPAGE, LiteSpeed no-cache and no-store; HTML responses send Vary: Accept.<tags> and & in titles were corrupted by output escaping.url values; subdirectory installs doubled the path.<!--nextpage--> returned every part on each page; shortcodes that echo output could break the response headers.<main> element kept only the first post, entry titles were removed, and Group block content disappeared on classic themes; page-builder indentation no longer turns lines into code blocks.Accept q-values and wildcards are respected (RFC 9110); POST requests are left alone; HEAD requests on archives return Markdown headers./feedback/ were wrongly excluded; very large inline scripts could empty the output; blank lines inside code blocks were collapsed.X-Robots-Tag: noindex is now sent only on .md URLs, not on negotiated responses for the page's own URL.umay-ai-markdown to markdown-negotiator across plugin header, gettext calls, and POT file (per WP.org review feedback).markdown-negotiator (per WordPress.org review feedback to ensure the name is distinctive).libxml_disable_entity_loader() calls in ContentExtractor and Converter. XXE protection unchanged: libxml 2.9+ disables external entities by default, and LIBXML_NONET is still passed to loadHTML().ob_start( $callback ) on template_redirect with a template_include filter that opens and closes its buffer (ob_start paired with ob_get_clean) inside a single function scope, satisfying Plugin Check's buffer-pairing requirement.try/catch around the template render so a fatal in a theme/plugin returns a clean 500 instead of a half-buffered response.<style>, <script>, <noscript> blocks via regex (defense in depth on top of league/html-to-markdown's remove_nodes).data-lazy-src / data-src / data-original and the first srcset URL into the real src. Drops empty/placeholder images.href="#elementor-action:...", javascript:, or bare # are unwrapped to plain text./languages/.