Umay AI Markdown
AI agents and assistants (ChatGPT, Claude, Perplexity, Gemini, coding agents such as Claude Code) read Markdown far more efficiently than HTML: no menus, scripts or styles, a fraction of the tokens. Umay AI Markdown gives every public page of your site a clean Markdown version and makes it easy for
AI agents and assistants (ChatGPT, Claude, Perplexity, Gemini, coding agents such as Claude Code) read Markdown far more efficiently than HTML: no menus, scripts or styles, a fraction of the tokens. Umay AI Markdown gives every public page of your site a clean Markdown version and makes it easy for agents to find.
Browsers, search engines and every client that does not ask for Markdown receive your normal, unchanged HTML. There is no settings page and no external service.
Three ways agents get Markdown
- Content negotiation — a request for any page with Accept: text/markdown receives Markdown on the same URL.
- .md URLs — /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.
- /llms.txt — an automatically generated llms.txt index of your pages, posts and public custom post types, each linked to its .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
- Zero configuration — install, activate, done.
- Hybrid extraction: 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.
- Powered by league/html-to-markdown .
- 12-hour cache per URL, invalidated automatically when content, terms, menus, templates or the theme change.
- Respects WordPress: password-protected, private, draft and missing pages are never served as Markdown. Canonical redirects, login walls, maintenance modes (503) and coming-soon templates apply first.
- Page-cache safe: Markdown responses are never stored by LiteSpeed Cache, WP Rocket, W3 Total Cache and other page caches.
- Agent-friendly headers: X-Markdown-Tokens (token estimate), Link: rel="canonical" , Content-Language , Last-Modified , Vary: Accept , and optional Content-Signal .
- Per-IP rate limit for uncached renders (30 per minute by default).
- No external requests, no telemetry, no database tables.
Filters
- 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.
Action: do_action( 'umay_mdn_flush' ) clears the Markdown cache.
Screenshots & previews.
- 01Install from Plugins > Add New , or upload the ZIP via Plugins > Add New > Upload Plugin .
- 02Activate the plugin. There is no settings page; it works immediately.
To verify:
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.
Frequently asked questions.
Does it slow down my site?
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.
Does this plugin contact any external services?
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.
Does it work with LiteSpeed Cache, WP Rocket, W3 Total Cache or Cloudflare?
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.
My SEO plugin also creates llms.txt. Which one is used?
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' ); .
Is the Markdown indexed by search engines? Will it cause duplicate content?
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.
Are password-protected or private posts exposed?
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.
Does it respect maintenance mode, coming-soon pages and login walls?
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.
How do I state my AI usage preferences (Content Signals)?
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.
How is the cache cleared?
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" );' .