{"id":285343,"date":"2026-03-11T19:23:18","date_gmt":"2026-03-11T19:23:18","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/ai-only-pages\/"},"modified":"2026-03-12T07:40:40","modified_gmt":"2026-03-12T07:40:40","slug":"ai-only-pages","status":"publish","type":"plugin","link":"https:\/\/twd.wordpress.org\/plugins\/ai-only-pages\/","author":23458605,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_crdt_document":"","version":"1.3.3","stable_tag":"1.3.3","tested":"6.7.5","requires":"5.5","requires_php":"7.4","requires_plugins":null,"header_name":"AI-Only Pages","header_author":"AI-Only Pages","header_description":"Mark any page as AI-only. Hidden from search engines, optimized for AI crawlers, listed in \/llms-index.txt.","assets_banners_color":"c5c9d5","last_updated":"2026-03-12 07:40:40","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"","header_author_uri":"","rating":0,"author_block_rating":0,"active_installs":0,"downloads":190,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.3.2":{"tag":"1.3.2","author":"tommyoz12","date":"2026-03-11 19:50:28"},"1.3.3":{"tag":"1.3.3","author":"tommyoz12","date":"2026-03-12 07:40:40"}},"upgrade_notice":{"1.3.3":"<p>Fixes missing admin.js (Block All toggle was broken in 1.3.2). Adds uninstall.php for clean removal and LICENSE file. Recommended update.<\/p>","1.3.2":"<p>Fixes unclosed output buffer reported by WordPress.org Plugin Review Team. Recommended update for stability.<\/p>","1.3.1":"<p>Critical bug fix: noindex tags were silently skipped on all WordPress Pages and non-post custom post types. Update immediately if you are using this plugin on Pages. Also fixes the settings page not appearing in the admin sidebar and settings styles not loading on some server configurations.<\/p>","1.3.0":"<p>Adds a global settings page under &quot;AI-Only Pages&quot; in the WordPress admin sidebar. All existing defaults are preserved \u2014 no behaviour changes unless you visit the settings page and make adjustments.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3482256,"resolution":"128x128","location":"assets","locale":""},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3482256,"resolution":"256x256","location":"assets","locale":""},"icon.svg":{"filename":"icon.svg","revision":3482271,"resolution":false,"location":"assets","locale":false}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3482262,"resolution":"1544x500","location":"assets","locale":""},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3482262,"resolution":"772x250","location":"assets","locale":""},"banner.svg":{"filename":"banner.svg","revision":3482268,"resolution":false,"location":"assets","locale":false}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.3.2","1.3.3"],"block_files":[],"assets_screenshots":[],"screenshots":{"1":"The AI-Only Pages global settings page \u2014 Instructions &amp; Status card with live \/llms-index.txt URL.","2":"Token Diet Master Control card with the on\/off toggle.","3":"Granular Token Diet Stripping \u2014 all six toggles with descriptions.","4":"The AI-Only Pages meta box in the WordPress block editor sidebar.","5":"The \/llms-index.txt file served with site and page metadata.","6":"Admin notice when a caching plugin is detected."},"jetpack_post_was_ever_published":false},"plugin_section":[],"plugin_tags":[2353,226124,6364,6499,186],"plugin_category":[55],"plugin_contributors":[257546],"plugin_business_model":[],"class_list":["post-285343","plugin","type-plugin","status-publish","hentry","plugin_tags-ai","plugin_tags-llm","plugin_tags-noindex","plugin_tags-robots","plugin_tags-seo","plugin_category-seo-and-marketing","plugin_contributors-tommyoz12","plugin_committers-tommyoz12"],"banners":{"banner":"https:\/\/ps.w.org\/ai-only-pages\/assets\/banner-772x250.png?rev=3482262","banner_2x":"https:\/\/ps.w.org\/ai-only-pages\/assets\/banner-1544x500.png?rev=3482262","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":"https:\/\/ps.w.org\/ai-only-pages\/assets\/icon.svg?rev=3482271","icon":"https:\/\/ps.w.org\/ai-only-pages\/assets\/icon.svg?rev=3482271","icon_2x":false,"generated":false},"screenshots":[],"raw_content":"<!--section=description-->\n<p>AI-Only Pages gives you granular control over which search engine bots can index each page on your WordPress site \u2014 while simultaneously making those pages more discoverable and useful for AI crawlers like ChatGPT, Claude, and Perplexity.<\/p>\n\n<p><strong>The core idea:<\/strong> you have content that is perfect for AI training pipelines and retrieval-augmented generation (RAG) systems, but you do not want that content competing for rankings in Google, Bing, or Yahoo.<\/p>\n\n<p>AI-Only Pages lets you mark those pages as AI-only: they disappear from traditional search engine indexes while becoming first-class citizens in the AI ecosystem.<\/p>\n\n<h4>What it does<\/h4>\n\n<ul>\n<li><strong>Per-bot noindex<\/strong> \u2014 Block individual bots (Googlebot, Bingbot, Yandexbot, etc.) with a checkbox per bot per page. Checking one bot blocks it; the others still index normally.<\/li>\n<li><strong>\"Block All\" master toggle<\/strong> \u2014 One click blocks all 10 supported search engine bots simultaneously.<\/li>\n<li><strong><code>&lt;meta&gt;<\/code> tags and HTTP headers<\/strong> \u2014 Both <code>&lt;meta name=\"googlebot\" content=\"noindex, nofollow\"&gt;<\/code> HTML meta tags and <code>X-Robots-Tag<\/code> HTTP headers are emitted, covering all crawling contexts. Works correctly on all public post types including Pages and custom post types.<\/li>\n<li><strong>SEO plugin integration<\/strong> \u2014 Suppresses Yoast SEO, WP Core, and RankMath's global <code>&lt;meta name=\"robots\"&gt;<\/code> tag on AI-only pages so there is no conflict between the global tag and your per-bot tags.<\/li>\n<li><strong>Sitemap exclusion<\/strong> \u2014 AI-Only pages are automatically removed from all XML sitemaps (Yoast SEO and WP Core sitemaps are both supported).<\/li>\n<li><strong><code>\/llms-index.txt<\/code><\/strong> \u2014 A plain-text AI discovery file served at <code>yoursite.com\/llms-index.txt<\/code> listing all AI-only pages with their titles and last-modified dates. AI crawlers can use this file to find your AI-optimised content directly. Can be toggled on\/off from the settings page.<\/li>\n<li><strong>Token Diet \u2014 clean AI output<\/strong> \u2014 When an AI crawler visits an AI-only page, the plugin serves a cleaned version of the HTML with navigation, sidebars, footers, cookie banners, inline styles, SVGs, and iframes stripped out. AI models receive pure content with minimal noise.<\/li>\n<li><strong>Global Settings Page<\/strong> \u2014 A top-level \"AI-Only Pages\" menu in the WordPress admin sidebar lets you configure Token Diet and LLM Index behaviour globally, without touching code.<\/li>\n<li><strong>Caching plugin notice<\/strong> \u2014 If WP Rocket, LiteSpeed Cache, or another full-page caching plugin is detected, an admin notice explains how to configure it to work alongside this plugin.<\/li>\n<\/ul>\n\n<h4>The Settings Page<\/h4>\n\n<p>A full settings page is available under <strong>AI-Only Pages<\/strong> in the WordPress admin sidebar. It provides:<\/p>\n\n<p><strong>Section 1 \u2014 Instructions &amp; Status:<\/strong> A \"How It Works\" guide covering the meta box, Token Diet, and LLM Index. A live, clickable URL to your <code>\/llms-index.txt<\/code> file with a green\/red status indicator showing whether the index is active.<\/p>\n\n<p><strong>Section 2 \u2014 LLM Index Settings:<\/strong> A toggle to enable or disable <code>\/llms-index.txt<\/code> globally. When disabled, the endpoint returns a 404.<\/p>\n\n<p><strong>Section 3 \u2014 Token Diet Master Control:<\/strong> A master toggle to enable or disable Token Diet entirely. When off, AI bots receive raw, full HTML \u2014 identical to what human visitors see.<\/p>\n\n<p><strong>Section 4 \u2014 Granular Token Diet Stripping:<\/strong> Individual toggles for each category of content stripped:<\/p>\n\n<ul>\n<li>Strip structural layout (headers, footers, sidebars, navigation, cookie banners)<\/li>\n<li>Strip <code>&lt;style&gt;<\/code> tags and embedded CSS<\/li>\n<li>Strip <code>&lt;svg&gt;<\/code> elements (major token bloaters)<\/li>\n<li>Strip <code>&lt;iframe&gt;<\/code> elements (maps, embeds, social widgets)<\/li>\n<li>Strip <code>&lt;form&gt;<\/code> elements <em>(Warning: removes WooCommerce Add to Cart buttons)<\/em><\/li>\n<li>Strip <code>&lt;script&gt;<\/code> tags <em>(Note: <code>application\/ld+json<\/code> schema is always preserved)<\/em><\/li>\n<\/ul>\n\n<h4>Supported Search Engine Bots<\/h4>\n\n<p>Googlebot (Web), Googlebot-Image, Googlebot-News, Googlebot-Video, AdsBot-Google, Bingbot, Slurp (Yahoo), DuckDuckBot, Baiduspider, YandexBot.<\/p>\n\n<h4>AI Bots Welcomed<\/h4>\n\n<p>GPTBot, ChatGPT-User, ClaudeBot, PerplexityBot, YouBot, Meta-ExternalAgent, Amazonbot, Bytespider, Diffbot, cohere-ai, anthropic-ai, AI2Bot, OAI-SearchBot, and more. These bots are detected automatically and served cleaned content when they visit an AI-only page.<\/p>\n\n<h4>Developer-Friendly<\/h4>\n\n<p>Every major behaviour is extensible via WordPress filters. See the <strong>Developer Reference<\/strong> section below. The Settings class hooks into filters at priority 5, leaving priorities 10 and above free for developer overrides \u2014 so your custom <code>add_filter()<\/code> calls always win.<\/p>\n\n<h3>Using the Plugin<\/h3>\n\n<h4>Per-page control<\/h4>\n\n<ol>\n<li>Open any post or page in the WordPress editor.<\/li>\n<li>Find the <strong>AI-Only Pages<\/strong> meta box in the right sidebar.<\/li>\n<li>Check individual bots to block them, or use <strong>Block from ALL search engine bots<\/strong> to check all at once.<\/li>\n<li>Click <strong>Publish<\/strong> or <strong>Update<\/strong> to save. The noindex tags take effect immediately.<\/li>\n<li>Visit <code>yoursite.com\/llms-index.txt<\/code> to confirm your page appears in the AI content index.<\/li>\n<\/ol>\n\n<p><strong>Note:<\/strong> The master toggle requires JavaScript. The individual checkboxes always work regardless of JS state.<\/p>\n\n<h4>Global settings<\/h4>\n\n<ol>\n<li>Go to <strong>AI-Only Pages<\/strong> in the WordPress admin sidebar.<\/li>\n<li>Review the \"How It Works\" section and confirm your <code>\/llms-index.txt<\/code> URL is live.<\/li>\n<li>Use the <strong>LLM Index Settings<\/strong> card to enable or disable the discovery file.<\/li>\n<li>Use the <strong>Token Diet \u2014 Master Control<\/strong> card to enable or disable all output cleaning.<\/li>\n<li>Use the <strong>Token Diet \u2014 Granular Stripping<\/strong> card to select exactly which HTML elements are stripped from AI output.<\/li>\n<li>Click <strong>Save Settings<\/strong>.<\/li>\n<\/ol>\n\n<h3>Developer Reference<\/h3>\n\n<p>All filters are applied inside <code>AIOnly\\Pages\\Plugin<\/code>. The Settings class hooks at priority 5; standard developer priority is 10+.<\/p>\n\n<p><strong><code>aionly_ai_crawler_signatures<\/code><\/strong>\nArray of User-Agent substrings used for Layer 1 bot detection.\n    @param string[] $signatures\n    @return string[]<\/p>\n\n<p><strong><code>aionly_strip_selectors<\/code><\/strong>\nCSS-style selector strings passed to Pass 1 of Token Diet (structural removal). Supports element tag, #id, and .class (one class, no combinators).\n    @param string[] $selectors\n    @return string[]<\/p>\n\n<p><strong><code>aionly_strip_token_bloat_tags<\/code><\/strong>\nXPath query strings passed to Pass 2 of Token Diet (tag removal).\n    @param string[] $queries\n    @return string[]<\/p>\n\n<p><strong><code>aionly_allowed_attributes<\/code><\/strong>\nHTML attribute names kept on every element by Pass 3 of Token Diet. Everything else is stripped.\n    @param string[] $attributes\n    @return string[]<\/p>\n\n<p><strong><code>aionly_should_clean_output<\/code><\/strong>\nBoolean. Return false to disable Token Diet entirely for a specific post.\n    @param bool     $enabled  Default: true.\n    @param \\WP_Post $post\n    @return bool<\/p>\n\n<p><strong><code>aionly_enable_xrobots_headers<\/code><\/strong>\nBoolean. Return false to suppress X-Robots-Tag HTTP headers.\n    @param bool     $enabled  Default: true.\n    @param \\WP_Post $post\n    @return bool<\/p>\n\n<p><strong><code>aionly_cache_ttl<\/code><\/strong>\nFilter the transient TTL in seconds.\n    @param int $ttl  Default: 600 (10 minutes).\n    @return int<\/p>\n\n<p><strong><code>aionly_llms_index_lines<\/code><\/strong>\nFilter the array of text lines that make up llms-index.txt before output.\n    @param string[] $lines       Array of lines (including comment lines).\n    @param int[]    $active_ids  Post IDs included in the index.\n    @return string[]<\/p>\n\n<p><strong><code>aionly_supported_post_types<\/code><\/strong>\nArray of public post type slugs the plugin should support.\n    @param string[] $post_types\n    @return string[]<\/p>\n\n<p><strong><code>aionly_use_heuristic_bot_detection<\/code><\/strong>\nBoolean. Return false to disable Layer 2 heuristic bot detection.\n    @param bool $enabled  Default: true.\n    @return bool<\/p>\n\n<h4>Code Examples<\/h4>\n\n<p><strong>Disable heuristic bot detection (uptime monitors):<\/strong><\/p>\n\n<pre><code>add_filter( 'aionly_use_heuristic_bot_detection', '__return_false' );\n<\/code><\/pre>\n\n<p><strong>Preserve WooCommerce forms (developer override \u2014 wins over settings page):<\/strong><\/p>\n\n<pre><code>add_filter( 'aionly_strip_token_bloat_tags', function( $queries ) {\n    return array_filter( $queries, function( $q ) {\n        return $q !== '\/\/form';\n    } );\n} );\n<\/code><\/pre>\n\n<p><strong>Add a custom strip selector:<\/strong><\/p>\n\n<pre><code>add_filter( 'aionly_strip_selectors', function( $selectors ) {\n    $selectors[] = '.advertisement';\n    $selectors[] = '#newsletter-popup';\n    return $selectors;\n} );\n<\/code><\/pre>\n\n<p><strong>Keep <code>class<\/code> attributes in AI output:<\/strong><\/p>\n\n<pre><code>add_filter( 'aionly_allowed_attributes', function( $attrs ) {\n    $attrs[] = 'class';\n    return $attrs;\n} );\n<\/code><\/pre>\n\n<p><strong>Add a custom AI crawler signature:<\/strong><\/p>\n\n<pre><code>add_filter( 'aionly_ai_crawler_signatures', function( $sigs ) {\n    $sigs[] = 'FutureBot';\n    return $sigs;\n} );\n<\/code><\/pre>\n\n<p><strong>Restrict to specific post types:<\/strong><\/p>\n\n<pre><code>add_filter( 'aionly_supported_post_types', function( $types ) {\n    return [ 'post', 'page' ]; \/\/ Only posts and pages.\n} );\n<\/code><\/pre>\n\n<p><strong>Disable Token Diet on a specific post (always wins, priority 10 &gt; settings priority 5):<\/strong><\/p>\n\n<pre><code>add_filter( 'aionly_should_clean_output', function( $enabled, $post ) {\n    if ( 42 === $post-&gt;ID ) {\n        return false; \/\/ Post 42 serves full HTML to AI bots.\n    }\n    return $enabled;\n}, 10, 2 );\n<\/code><\/pre>\n\n<p><strong>Read a single setting value in custom code:<\/strong><\/p>\n\n<pre><code>$token_diet_on = '1' === \\AIOnly\\Pages\\Settings::get( 'token_diet_enabled' );\n$all_settings  = \\AIOnly\\Pages\\Settings::get_settings(); \/\/ Full array.\n<\/code><\/pre>\n\n<!--section=installation-->\n<h4>Automatic installation<\/h4>\n\n<ol>\n<li>In your WordPress admin, go to <strong>Plugins \u2192 Add New<\/strong>.<\/li>\n<li>Search for \"AI-Only Pages\".<\/li>\n<li>Click <strong>Install Now<\/strong>, then <strong>Activate<\/strong>.<\/li>\n<li>After activation, go to <strong>Settings \u2192 Permalinks<\/strong> and click <strong>Save Changes<\/strong> to flush rewrite rules so <code>\/llms-index.txt<\/code> begins working immediately.<\/li>\n<li>Visit <strong>AI-Only Pages<\/strong> in the sidebar to configure global settings.<\/li>\n<\/ol>\n\n<h4>Manual installation<\/h4>\n\n<ol>\n<li>Download the plugin zip file.<\/li>\n<li>Upload the <code>ai-only-pages<\/code> folder to <code>\/wp-content\/plugins\/<\/code>.<\/li>\n<li>Activate the plugin from the <strong>Plugins<\/strong> menu.<\/li>\n<li>Go to <strong>Settings \u2192 Permalinks<\/strong> and click <strong>Save Changes<\/strong>.<\/li>\n<\/ol>\n\n<h4>Plugin folder structure<\/h4>\n\n<p>After installation the plugin occupies exactly this structure inside\n    \/wp-content\/plugins\/ai-only-pages\/:<\/p>\n\n<pre><code>ai-only-pages\/\n\u251c\u2500\u2500 ai-only-pages.php           Root loader. Contains the plugin header WordPress\n\u2502                               reads for name\/version. Performs PHP and WP version\n\u2502                               gates. Registers activation\/deactivation hooks.\n\u2502                               Contains zero modern PHP syntax so it is safe to\n\u2502                               parse on PHP 5.x without fatal errors.\n\u2502\n\u251c\u2500\u2500 includes\/\n\u2502   \u251c\u2500\u2500 class-plugin.php        The core plugin class. All bot detection, meta\n\u2502   \u2502                           boxes, output buffering, Token Diet, LLM Index,\n\u2502   \u2502                           and SEO plugin overrides live here.\n\u2502   \u2502\n\u2502   \u2514\u2500\u2500 class-settings.php      The Settings class. Registers the top-level admin\n\u2502                               menu, the settings page, and all WordPress Settings\n\u2502                               API fields. Hooks into core plugin filters at\n\u2502                               priority 5 to alter behaviour dynamically from\n\u2502                               saved options.\n\u2502\n\u251c\u2500\u2500 assets\/\n\u2502   \u2514\u2500\u2500 js\/\n\u2502       \u2514\u2500\u2500 admin.js            Vanilla JavaScript for the meta box. Handles the\n\u2502                               \"Block from ALL\" master toggle and keeps it in\n\u2502                               sync with individual bot checkboxes. No jQuery.\n\u2502                               Enqueued only on post.php and post-new.php.\n\u2502\n\u251c\u2500\u2500 uninstall.php               Clean removal. Deletes all plugin options and\n\u2502                               post meta when the plugin is deleted via the\n\u2502                               WordPress admin Plugins screen.\n\u2502\n\u2514\u2500\u2500 readme.txt                  This file.\n<\/code><\/pre>\n\n<p><strong>Why this structure?<\/strong><\/p>\n\n<p>The split between <code>ai-only-pages.php<\/code> and <code>class-plugin.php<\/code> is intentional and critical. WordPress parses the root plugin file to read its header (<code>Plugin Name:<\/code>, <code>Version:<\/code>, etc.) before any PHP runs. If the root file used modern PHP syntax and the site ran PHP 7.0, WordPress would throw a fatal parse error before the version gate could display a helpful admin notice. Keeping the root file at PHP 5.4 syntax means the gate always runs and users always see a readable error instead of a white screen. Both <code>class-plugin.php<\/code> and <code>class-settings.php<\/code> use PHP 7.4+ syntax and are both loaded by the root loader after the version gates have passed. No manual wiring is required.<\/p>\n\n<!--section=faq-->\n<dl>\n<dt id=\"i%20blocked%20googlebot%20but%20google%20is%20still%20indexing%20the%20page.%20why%3F\"><h3>I blocked Googlebot but Google is still indexing the page. Why?<\/h3><\/dt>\n<dd><p>Google may have already crawled and cached the page before you activated the plugin. It can take days or weeks for Google to re-crawl and respect the new noindex directive. If you need faster removal, submit the URL to Google Search Console's URL Removal tool. Also verify that the noindex tag is actually appearing on the page: view source and search for <code>&lt;meta name=\"googlebot\"<\/code>.<\/p><\/dd>\n<dt id=\"the%20noindex%20tags%20are%20not%20appearing%20on%20my%20pages%20%28not%20posts%29.\"><h3>The noindex tags are not appearing on my Pages (not Posts).<\/h3><\/dt>\n<dd><p>This was a bug fixed in version 1.3.1. Both <code>output_noindex_tags()<\/code> and <code>output_xrobots_headers()<\/code> incorrectly checked <code>publicly_queryable<\/code> to decide whether to proceed. WordPress's built-in <code>page<\/code> post type has <code>publicly_queryable = false<\/code>, causing both methods to silently bail out without writing any tags. Update to 1.3.1 to resolve this.<\/p><\/dd>\n<dt id=\"does%20this%20work%20with%20yoast%20seo%20%2F%20rankmath%3F\"><h3>Does this work with Yoast SEO \/ RankMath?<\/h3><\/dt>\n<dd><p>Yes. The plugin overrides the global <code>&lt;meta name=\"robots\"&gt;<\/code> tag that Yoast SEO and RankMath output on AI-only pages. Without this override, Yoast might output <code>&lt;meta name=\"robots\" content=\"index, follow\"&gt;<\/code> which would conflict with the per-bot tags. On AI-only pages, the global tag is suppressed entirely; only the per-bot tags remain.<\/p><\/dd>\n<dt id=\"%2Fllms-index.txt%20shows%20a%20404.%20how%20do%20i%20fix%20it%3F\"><h3>\/llms-index.txt shows a 404. How do I fix it?<\/h3><\/dt>\n<dd><p>First, check that <strong>LLM Index<\/strong> is enabled on the <strong>AI-Only Pages settings page<\/strong>. If it is enabled, go to <strong>Settings \u2192 Permalinks<\/strong> and click <strong>Save Changes<\/strong> without changing anything. This flushes WordPress's rewrite rules, which registers the <code>\/llms-index.txt<\/code> URL pattern. This flush happens automatically on plugin activation, but some server configurations (particularly Nginx without <code>try_files<\/code>) may need a manual flush or a server config update.<\/p><\/dd>\n<dt id=\"my%20caching%20plugin%20is%20serving%20the%20same%20page%20to%20both%20humans%20and%20ai%20bots.\"><h3>My caching plugin is serving the same page to both humans and AI bots.<\/h3><\/dt>\n<dd><p>Full-page caching plugins (WP Rocket, LiteSpeed, W3 Total Cache, etc.) serve responses from a disk cache before WordPress runs. The plugin's output buffer never fires on cached pages.<\/p>\n\n<p>To fix this, configure your caching plugin to exclude AI-Only page URLs from its cache:<\/p>\n\n<p><strong>WP Rocket:<\/strong> Settings \u2192 Cache \u2192 Never Cache URL(s). Add the slug of each AI-only page.<\/p>\n\n<p><strong>LiteSpeed Cache:<\/strong> LiteSpeed Cache \u2192 Cache \u2192 Do not cache URIs.\n<strong>W3 Total Cache:<\/strong> Performance \u2192 Page Cache \u2192 Never cache the following pages.<\/p>\n\n<p>Alternatively, add a custom rule to exclude pages with the <code>_aionly_active<\/code> cookie, or contact your host's support team \u2014 managed WordPress hosts often expose this setting in their dashboard.<\/p><\/dd>\n<dt id=\"my%20uptime%20monitor%20or%20api%20client%20is%20being%20treated%20as%20an%20ai%20bot.\"><h3>My uptime monitor or API client is being treated as an AI bot.<\/h3><\/dt>\n<dd><p>The plugin uses two-layer bot detection. Layer 1 matches known AI crawler signatures. Layer 2 (heuristic) flags requests with no browser engine string in the User-Agent AND no <code>Accept-Language<\/code> header \u2014 a combination that every real browser always sends, but that many CLI tools and monitoring services do not. The simplest fix is to configure your monitoring tool to send an <code>Accept-Language<\/code> header. Alternatively, disable heuristic detection entirely:<\/p>\n\n<pre><code>add_filter( 'aionly_use_heuristic_bot_detection', '__return_false' );\n<\/code><\/pre><\/dd>\n<dt id=\"woocommerce%20add-to-cart%20buttons%20are%20missing%20on%20ai%20pages.%20is%20that%20normal%3F\"><h3>WooCommerce add-to-cart buttons are missing on AI pages. Is that normal?<\/h3><\/dt>\n<dd><p>Yes, if \"Strip <code>&lt;form&gt;<\/code> elements\" is enabled in the settings (it is by default). WooCommerce add-to-cart buttons are rendered inside <code>&lt;form&gt;<\/code> elements. AI crawlers cannot interact with forms anyway \u2014 they only read content. If you want AI crawlers to see your product CTAs, turn off \"Strip forms\" on the <strong>AI-Only Pages settings page<\/strong>, or add a developer filter:<\/p>\n\n<pre><code>add_filter( 'aionly_strip_token_bloat_tags', function( $queries ) {\n    return array_filter( $queries, function( $q ) {\n        return $q !== '\/\/form';\n    } );\n} );\n<\/code><\/pre><\/dd>\n<dt id=\"will%20disabling%20individual%20stripping%20toggles%20break%20the%20page%20for%20ai%20crawlers%3F\"><h3>Will disabling individual stripping toggles break the page for AI crawlers?<\/h3><\/dt>\n<dd><p>No. Disabling a toggle simply passes more of the original HTML through to the AI crawler. The page is never broken \u2014 it may just contain more noise that uses up the crawler's context window. The defaults are optimised for maximum signal-to-noise ratio.<\/p><\/dd>\n<dt id=\"do%20settings-page%20changes%20require%20me%20to%20flush%20permalinks%3F\"><h3>Do settings-page changes require me to flush permalinks?<\/h3><\/dt>\n<dd><p>No. Settings only affect the output buffer and filter callbacks \u2014 they have no impact on WordPress rewrite rules. Changes take effect on the very next AI crawler request.<\/p><\/dd>\n<dt id=\"where%20is%20the%20settings%20data%20stored%3F\"><h3>Where is the settings data stored?<\/h3><\/dt>\n<dd><p>All settings are stored in a single wp_options row with the key <code>aionly_pages_settings<\/code> as a serialised array. You can inspect or export it like any other WordPress option.<\/p><\/dd>\n<dt id=\"how%20does%20the%20settings%20class%20hook%20into%20the%20core%20plugin%3F\"><h3>How does the Settings class hook into the core plugin?<\/h3><\/dt>\n<dd><p>class-settings.php uses the same public <code>add_filter()<\/code> hooks that the core plugin exposes to developers. Specifically:<\/p>\n\n<ul>\n<li><code>aionly_should_clean_output<\/code> \u2014 used to disable Token Diet when the master toggle is off.<\/li>\n<li><code>aionly_strip_token_bloat_tags<\/code> \u2014 used to build a dynamic XPath query array from granular toggles.<\/li>\n<li><code>aionly_strip_selectors<\/code> \u2014 used to empty the structural selector list when layout stripping is off.<\/li>\n<li><code>template_redirect<\/code> at priority 0 \u2014 used to return a 404 for <code>\/llms-index.txt<\/code> when the LLM Index is disabled.<\/li>\n<\/ul>\n\n<p>All these hooks run at priority 5, which means developer overrides at priority 10 (the WordPress default) always take precedence. Your custom filters always win.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.3.3 \u2014 2026-03-11<\/h4>\n\n<ul>\n<li><strong>Fixed:<\/strong> Missing <code>assets\/js\/admin.js<\/code> \u2014 the meta box \"Block from ALL\" master toggle was non-functional in 1.3.2 due to the JavaScript file being omitted from the release package.<\/li>\n<li><strong>Added:<\/strong> <code>uninstall.php<\/code> \u2014 clean removal of all plugin data (<code>aionly_pages_settings<\/code> option and all <code>_aionly_*<\/code> post meta) when the plugin is deleted via the WordPress admin.<\/li>\n<li><strong>Added:<\/strong> <code>LICENSE<\/code> file (GPLv2 full text).<\/li>\n<li><strong>Updated:<\/strong> <code>Tested up to<\/code> bumped to WordPress 6.7.2.<\/li>\n<\/ul>\n\n<h4>1.3.2 \u2014 2026-03-01<\/h4>\n\n<ul>\n<li><strong>Fixed:<\/strong> Output buffer opened by Token Diet (<code>ob_start()<\/code>) is now explicitly closed via a <code>shutdown<\/code> hook, preventing potential buffer-stack conflicts with other plugins. Addresses WordPress.org Plugin Review Team feedback.<\/li>\n<\/ul>\n\n<h4>1.3.1 \u2014 2026-02-20<\/h4>\n\n<ul>\n<li><strong>Fixed:<\/strong> Noindex <code>&lt;meta&gt;<\/code> tags and <code>X-Robots-Tag<\/code> headers were not emitted on WordPress Pages and non-post custom post types. Both methods incorrectly checked <code>publicly_queryable<\/code> \u2014 WordPress's built-in <code>page<\/code> post type has this set to <code>false<\/code>, causing both to silently return without writing any tags. Fixed by checking <code>public<\/code> instead.<\/li>\n<li><strong>Fixed:<\/strong> Settings page CSS was not loading on some WordPress setups. <code>wp_add_inline_style()<\/code> was attached to the <code>wp-admin<\/code> handle which is not guaranteed to be registered in the required state. Fixed by registering a dedicated <code>aionly-settings-ui<\/code> handle and attaching inline CSS to that.<\/li>\n<li><strong>Fixed:<\/strong> Settings admin menu was not appearing because <code>class-settings.php<\/code> was missing its <code>require_once<\/code> in the root loader.<\/li>\n<li><strong>Fixed:<\/strong> Removed placeholder <code>Plugin URI<\/code> header pointing to a non-existent URL, which produced a broken \"Visit plugin site\" link in the Plugins list.<\/li>\n<li><strong>Cleaned:<\/strong> Removed dead <code>add_settings_section()<\/code> and <code>add_settings_field()<\/code> calls that had no effect since <code>do_settings_sections()<\/code> is never called.<\/li>\n<li><strong>Security:<\/strong> <code>$_SERVER['HTTP_USER_AGENT']<\/code> and <code>$_SERVER['HTTP_ACCEPT_LANGUAGE']<\/code> now passed through <code>sanitize_text_field( wp_unslash() )<\/code> before use.<\/li>\n<li><strong>i18n:<\/strong> Added <code>load_plugin_textdomain()<\/code> so translations load correctly.<\/li>\n<\/ul>\n\n<h4>1.3.0 \u2014 2026-02-19<\/h4>\n\n<ul>\n<li><strong>New:<\/strong> <code>includes\/class-settings.php<\/code> \u2014 full WordPress Settings API integration adding a top-level \"AI-Only Pages\" admin menu with four visual cards.<\/li>\n<li><strong>New:<\/strong> LLM Index toggle \u2014 enable\/disable <code>\/llms-index.txt<\/code> globally. When disabled, the endpoint returns a 404.<\/li>\n<li><strong>New:<\/strong> Token Diet master toggle \u2014 enable\/disable all AI output cleaning globally without touching code.<\/li>\n<li><strong>New:<\/strong> Six granular Token Diet toggles \u2014 independently control stripping of structural layout, <code>&lt;style&gt;<\/code> tags, <code>&lt;svg&gt;<\/code> elements, <code>&lt;iframe&gt;<\/code> elements, <code>&lt;form&gt;<\/code> elements, and <code>&lt;script&gt;<\/code> tags (with the schema <code>application\/ld+json<\/code> preservation guarantee always enforced).<\/li>\n<li><strong>New:<\/strong> Live <code>\/llms-index.txt<\/code> URL displayed on the settings page with a green\/red status badge.<\/li>\n<li><strong>New:<\/strong> \"How It Works\" explainer built into the settings page \u2014 no need to consult the readme for basic orientation.<\/li>\n<li><strong>Architecture:<\/strong> Settings class hooks into core plugin filters at priority 5, ensuring developer <code>add_filter()<\/code> calls at priority 10+ always override settings-page values.<\/li>\n<li><strong>Architecture:<\/strong> All settings stored in one <code>wp_options<\/code> row (<code>aionly_pages_settings<\/code>) to minimise database overhead.<\/li>\n<\/ul>\n\n<h4>1.2.1 \u2014 2026-02-19<\/h4>\n\n<ul>\n<li><strong>Fixed:<\/strong> Restored Yoast SEO, WP Core, and RankMath global robots override filters that were inadvertently removed in v1.2.0. Without these, Yoast's global <code>&lt;meta name=\"robots\" content=\"index, follow\"&gt;<\/code> tag overrode per-bot noindex tags \u2014 the core feature was broken for sites using Yoast or RankMath.<\/li>\n<li><strong>Fixed:<\/strong> Double-encoding bug in the \"AI-optimized &amp; listed\" status badge. <code>esc_html_e()<\/code> was applied to a string already containing <code>&amp;amp;<\/code>, producing <code>&amp;amp;amp;<\/code> which rendered as literal \"&amp;\" in the browser.<\/li>\n<li><strong>Fixed:<\/strong> <code>save_meta_data()<\/code> now always syncs the <code>_aionly_active<\/code> derived flag on every valid save, not only when individual bot values change. This self-heals any flag desync caused by direct DB edits, imports, or third-party plugins.<\/li>\n<li><strong>Fixed:<\/strong> Pro upsell link now includes <code>rel=\"noopener noreferrer\"<\/code> on <code>target=\"_blank\"<\/code> to prevent reverse tabnapping.<\/li>\n<li><strong>Improved:<\/strong> <code>admin.js<\/code> now listens to the <code>change<\/code> event instead of <code>click<\/code>. The <code>change<\/code> event is the semantically correct event for checkbox state and handles keyboard (Space bar) and programmatic changes correctly.<\/li>\n<li><strong>Improved:<\/strong> Added <code>function_exists()<\/code> guards to all global functions in the root file to prevent \"Cannot redeclare function\" fatal errors if the file is somehow processed more than once.<\/li>\n<\/ul>\n\n<h4>1.2.0 \u2014 2026-02-19<\/h4>\n\n<ul>\n<li><strong>Fixed:<\/strong> Asset path bug \u2014 PHP enqueued <code>assets\/js\/admin.js<\/code> but the file was located at <code>assets\/admin.js<\/code>. The JS file 404'd and the \"Block from ALL\" button was dead on arrival.<\/li>\n<li><strong>Fixed:<\/strong> <code>DOMContentLoaded<\/code> wrapper removed from <code>admin.js<\/code>. Scripts enqueued with <code>in_footer=true<\/code> execute after that event fires; the callback was never running.<\/li>\n<li><strong>Fixed:<\/strong> <code>admin.js<\/code> now uses <code>classList<\/code> API instead of fragile <code>className.indexOf()<\/code> string matching for class detection.<\/li>\n<li><strong>Fixed:<\/strong> Restored missing <code>before_delete_post<\/code> cache-clearing logic that was inadvertently merged with <code>transition_post_status<\/code> into a single variadic function.<\/li>\n<li><strong>Fixed:<\/strong> Heuristic bot detection (Layer 2) restored after it was silently removed in a prior refactor.<\/li>\n<li><strong>Fixed:<\/strong> <code>junk_queries<\/code> loop now uses <code>iterator_to_array()<\/code> to snapshot the live <code>DOMNodeList<\/code> before iterating. Iterating a live list while removing nodes caused silent skips.<\/li>\n<li><strong>Improved:<\/strong> All inline attribute iteration now collects attributes into an array before removal, preventing NamedNodeMap reindexing skips.<\/li>\n<li><strong>Improved:<\/strong> Post ID resolved explicitly from <code>$_GET['post']<\/code> \/ <code>$_POST['post_ID']<\/code> in <code>enqueue_admin_assets()<\/code>, removing reliance on the implicit global <code>$post<\/code>.<\/li>\n<\/ul>\n\n<h4>1.1.6 \u2014 2026-02-19<\/h4>\n\n<ul>\n<li>Major stability pass following an external AI code review that missed three deployment-blocking bugs while reporting \"zero bugs found.\"<\/li>\n<li>Fixed JS path (assets\/js\/ subfolder), DOMContentLoaded timing, and class name mismatch between PHP and JS.<\/li>\n<\/ul>\n\n<h4>1.1.3 \u2014 2026-02-19<\/h4>\n\n<ul>\n<li>Fixed <code>plugin_dir_url()<\/code> path calculation using <code>dirname(dirname(__FILE__))<\/code>.<\/li>\n<li>Fixed XPath injection via unsanitized filter values in <code>aionly_strip_token_bloat_tags<\/code>.<\/li>\n<li>Fixed <code>get_post()<\/code> fragility \u2014 now resolves post ID explicitly from request superglobals.<\/li>\n<\/ul>\n\n<h4>1.1.2 \u2014 2026-02-19<\/h4>\n\n<ul>\n<li>Introduced Token Diet V2 with three-pass HTML cleaning (structural, bloat, attributes).<\/li>\n<li>Added master \"Block All\" toggle with JavaScript event delegation.<\/li>\n<li>Added X-Robots-Tag HTTP headers alongside HTML meta tags.<\/li>\n<li>Added Yoast SEO, WP Core, and RankMath robots override filters.<\/li>\n<li>Added heuristic bot detection layer (no browser UA markers + no Accept-Language).<\/li>\n<\/ul>\n\n<h4>1.1.0<\/h4>\n\n<ul>\n<li>Added \/llms-index.txt discovery file with transient caching.<\/li>\n<li>Added caching plugin compatibility notice.<\/li>\n<li>Added sitemap exclusion for Yoast SEO and WP Core sitemaps.<\/li>\n<\/ul>\n\n<h4>1.0.3<\/h4>\n\n<ul>\n<li>Fixed nonce verification \u2014 nonces are post-specific to prevent cross-post replay.<\/li>\n<li>Fixed capability check using post type's own capability type.<\/li>\n<li>Added <code>transition_post_status<\/code> hook to clear transient cache on post status changes.<\/li>\n<\/ul>\n\n<h4>1.0.1<\/h4>\n\n<ul>\n<li>Initial public release.<\/li>\n<li>Per-bot noindex meta box with 10 supported search engine bots.<\/li>\n<li>Transient-cached active post ID query.<\/li>\n<li>Activation\/deactivation rewrite rule management.<\/li>\n<\/ul>","raw_excerpt":"Mark any page as AI-only. Hidden from search engines, optimized for AI crawlers, listed in \/llms-index.txt. Includes Token Diet and global settings.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/twd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/285343","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/twd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/twd.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/twd.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=285343"}],"author":[{"embeddable":true,"href":"https:\/\/twd.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/tommyoz12"}],"wp:attachment":[{"href":"https:\/\/twd.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=285343"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/twd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=285343"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/twd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=285343"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/twd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=285343"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/twd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=285343"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/twd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=285343"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}