Control search engine indexing with robots directives using HTML meta tags or HTTP headers.
Information
Author:
ROBOTSTXTDownload
Price:
€0.00Requirements
Version:
3.7.0WordPress:
7.0–
7.2PHP >=
8.3Changelog
3.7.0
Release date: 2026-11-03
Highlights
- Import from other SEO plugins (spec section 5): on-demand migration of the noindex / nofollow flags stored by Yoast SEO, Rank Math, SEOPress and All in One SEO into noindex SEO per-post and per-term overrides — detected from data presence (the source plugin does not need to be installed), user-confirmed, and never overwriting content that already has its own override.
- Virtual robots.txt management (spec section 7): block (
Disallow: /) or allow (Allow: /) individual crawlers, appended to the robots.txt WordPress generates virtually — never replacing it — with one-click presets for the well-known AI and search-engine crawlers. Inactive by default.
Added
- Import from other plugins. A new section on the Import / Export tab lists every supported source plugin together with how many posts and terms carry its robots flags, and imports the checked ones on explicit confirmation. The runner converts source flags into granular overrides (
_noindex_seo_override=1plus noindex / nofollow), skips and reports content that already carries a noindex SEO override row (an explicit “off” counts as data), re-verifies LIKE-based matches (Rank Math’s serialized arrays) per post so false positives are dropped, and whitelists source slugs server-side. Sources are data-driven and filterable (noindex_seo_import_sources) so third parties can register or correct one. - Robots.txt tab. A fourth settings tab manages up to 30 bot rules (User-agent token + block/allow policy;
wildcard supported, duplicates collapse, agents stripped to the robots.txt-legal charset). The rules are appended through core’srobots_txtfilter only while the feature is enabled, a live preview shows the exact block appended, and preset buttons add the well-known AI crawlers (GPTBot, ClaudeBot, Google-Extended, CCBot, Bytespider… — filterable vianoindex_seo_ai_bots) or the main search engine crawlers (noindex_seo_search_bots) as blocked rules. The core output — and a physical robots.txt or “Discourage search engines” — always takes precedence. - The robots.txt settings ride the consolidated option and the JSON export/import.
Changed
- The settings page gained a fourth tab (General / SEO Settings / Import-Export / Robots.txt); the rule-table JavaScript is now shared between the conditional rules and the robots.txt tables, scoped per container.
Fixed
- Block editor granular overrides now save on custom post types registered without
custom-fieldssupport (a very common CPT registration). Without that support the REST API exposes nometafield for the post type, so the editor sidebar’s saves were silently dropped with an HTTP 200 — the override checkbox appeared to do nothing. The plugin now addscustom-fieldssupport for exactly the post types it registers meta on (while per-post granular control is enabled).
Security
- No new attack surface: the import handler is
manage_options+ nonce gated with whitelisted sources, writes only through the meta API after re-reading each source flag, and skips content with existing override data; robots.txt agents are charset-stripped (no newline or comment injection into the file) and policies are whitelisted; the tab form rides the existing nonce + capability path. PHPCS/PHPStan/composer audit clean.
Compatibility
- WordPress: 7.0 – 7.2
- PHP: 8.3 – 8.6
Tests
- PHPUnit: 9.6.38 (230 tests, 1152 assertions, all passing) on the WordPress 7.2-beta3 test library, run under PHP 8.3, 8.4, 8.5 and 8.6.0RC3 — including the new robots.txt sanitization/output/filter/form suites and the import detection/conversion/never-overwrite/false-positive/handler suites.
- PHPCompatibility: 0 errors on the declared range 8.3-8.6 and on the full review range 5.6-8.6.
- PHPCS (WordPress-Core, -Docs, -Extra) and PHPStan level 9: 0 errors.
- Translations: Catalan and Spanish catalogs regenerated and 100% complete (209 strings each).
3.6.0
Release date: 2026-11-03
Highlights
- Modern robots directives (TODO #5):
notranslate,max-snippet,max-image-preview,max-video-previewandunavailable_afterjoin the five existing booleans — on every surface: the global context grid, per-post and per-term granular overrides, Quick Edit, the block editor sidebar, the REST/effectiveendpoint and WP-CLI (name=valuetokens). - Conditional auto-noindex rules (TODO #11): apply
noindexautomatically to any post whose custom field (for example an ACF field) exactly matches a value; matched posts also drop out of the core XML sitemap. - Multisite behavior documented (TODO #9, docs variant): per-site by design, with an export/import-based rollout recipe.
Added
- Value directives everywhere. Boolean directives are flags; value directives carry a value and render as
name:value(max-snippet:-1,max-image-preview:large,unavailable_after: Tue, 01 Jan 2030 09:00:00 +0000in site time with a numeric UTC offset). They are configured with typed inputs (number, size select,datetime-local) on the settings grid, the post meta box, Quick Edit, the term panel and the Gutenberg sidebar; stored in the consolidated option (contexts) and in_noindex_seo_{directive}post/term meta; registered with the REST API (register_post_meta/register_term_metawith per-type sanitization); exported/imported with normalization; and supported by WP-CLI —wp noindex-seo set archive max-snippet=-1 max-image-preview=large,wp noindex-seo unset archive max-snippet(plain name clears the value), andlistreports the configured value (max-snippet:-1) oroff. notranslateas the sixth boolean directive, including a matching SVG icon in the plugin’s icon registry and membership in the “Apply Recommended Defaults” set (which enables the six booleans; value directives stay untouched — “enabling” them without a value is meaningless).- Conditional rules (TODO #11). A “Conditional rules” card on the SEO Settings tab manages up to 20 exact-match rules (custom field name + value; rows with empty field/value are ignored, and the plugin’s own
_noindex_seo_meta keys are rejected). Rules run on the front-end below an explicit per-post override and above per-CPT/global defaults; matched posts surface assource: auto_rulein/effectiveand are excluded from the pages sitemap. The rules read plain post meta, so no ACF installation is required. - A
notranslateSVG directive icon, plus badge support for value directives in the Robots columns (max-snippet:-1tokens).
Changed
noindex_seo_metarobots()now takes a directive map (name => true|value); a flat list of boolean names (the pre-3.6.0 third-party calling convention) is still accepted and normalized. Thewp_robotsintegration passes directive values through to core ($robots['max-snippet'] = '-1').noindex_seo_get_setting()returns the normalized string value for value directives (” = off) instead of coercing everything to 0/1; the hourly transient cache stores values the same way.- The WP-CLI
set/unsetdefault (no directive arguments) now enables/disables the six boolean directives; value directives always require explicitname=valuetokens (a value on a boolean, or an invalid/missing value, is rejected with a descriptive error). Datetime values need quoting or aTseparator because of the space:unavailable_after=2030-01-01T09:00. - Block editor sidebar labels dropped their emoji prefixes (matching the 3.5.0 admin UI icon direction), and the sidebar gained controls for all five new directives.
Fixed
- Nothing.
Security
- No new attack surface: rules and value directives ride the existing nonce + capability paths (settings form:
manage_options+check_admin_referer; meta box/Quick Edit:edit_post; term panel:manage_categories); every value normalizes through a single whitelist-driven normalizer (integers ≥ -1, a none/standard/large enum, a parse-validated datetime); rule fields strip everything but[A-Za-z0-9_-]and values go throughsanitize_text_field(); sitemap rule clauses use WP_Query’smeta_query(prepared by core). PHPCS/PHPStan/composer audit clean.
Compatibility
- WordPress: 7.0 – 7.2
- PHP: 8.3 – 8.6
Tests
- PHPUnit: 9.6.38 (216 tests, 1107 assertions, all passing) on the WordPress 7.2-beta3 test library, run under PHP 8.3, 8.4, 8.5 and 8.6.0RC3 — including the new registry/normalization, settings-merge, form-roundtrip, front-end value output, granular value overrides, REST formatted tokens, CLI name=value, rules sanitization/matching/precedence/sitemap/auto_rule suites.
- PHPCompatibility: 0 errors on the declared range 8.3-8.6 and on the full review range 5.6-8.6.
- PHPCS (WordPress-Core, -Docs, -Extra) and PHPStan level 9: 0 errors.
- Translations: Catalan and Spanish catalogs regenerated and 100% complete (188/187 strings), including the previously untranslated strings found during the merge.
3.5.0
Release date: 2026-11-03
Highlights
- WooCommerce special-page contexts (TODO #8): dedicated directives for the WooCommerce Cart, Checkout and My Account pages, detected through the page-ID options (no WooCommerce code needed), recommended by default, and mirrored across the front-end, the REST
/effectiveendpoint and the core XML sitemaps. - Compatibility window narrowed to WordPress 7.0 – 7.2 and PHP 8.3 – 8.6:
Requires at leastraised from 4.7 to 7.0,Requires PHPraised from 5.6 to 8.3,Tested up toset to WordPress 7.2 (verified on 7.2-beta3) and PHP support documented through 8.6 (verified on 8.6.0RC3). - Fixed
wp noindex-seo list, which fatalled on every real WP-CLI invocation since 3.4.0 (nonexistent static API call;--contextfilter renamed to--onlyto avoid WP-CLI 2.11+’s reserved global flag). - Directive icons modernized for the SVG era: on WordPress 7.2+ the five directive icons render through the core Icons API — the same system WordPress 7.2 uses for its own admin chrome — while WordPress 7.0 – 7.1 keep the legacy emoji glyphs as a retrocompatible fallback.
Added
- WooCommerce contexts (
woo_cart,woo_checkout,woo_myaccount): the Cart, Checkout and My Account pages are regular pages that conventionally deserve noindex but previously fell into the genericpagebucket. Each now has its own context with all five directives, a “WooCommerce” section on the SEO Settings tab (rendered only when the store pages are configured), and Recommended badges — the three contexts also joined the “Apply Recommended Defaults” set (now 15 contexts). - Detection is option-driven (
woocommerce_cart_page_id,woocommerce_checkout_page_id,woocommerce_myaccount_page_id): no WooCommerce class or function dependency; without WooCommerce the contexts are inert. My Account endpoints (order history, addresses) and the checkout order-pay / order-received endpoints share their parent page’s ID, so they are covered automatically. - Precedence: the dedicated contexts run before the generic
pagecontext on the front-end and in/wp-json/noindex-seo/v1/effective, so a Woo page with directives gets them instead of the page-level ones; with no dedicated directives the page context still applies. - Sitemap: special pages whose context has
noindex=1drop out of the pages sitemap even when thepagecontext is not noindexed. - SVG directive icons (retrocompatible). The five robots-directive icons render as color-adaptive 16px SVGs through the WordPress Icons API (
wp_register_icon()/wp_get_icon()), registered in a privatenoindex-seoicon collection — everywhere they appear: the settings grid, the post meta box, Quick Edit, and the post and taxonomy list-table Robots columns. The SVG path is gated to WordPress 7.2+, whose icon sanitizer understands the stroke-based designs (rect/circle,strokeattributes) and whose registry supports private collections; on WordPress 7.0 – 7.1 the legacy emoji glyphs remain (7.1’s narrower sanitizer would strip the SVGs to unstyled fragments), so rendering stays correct on every supported version — and consistent on every OS, unlike the emoji (which each platform draws differently). Follows the core direction of replacing admin dashicons with SVG (wordpress-develop#12270, landed in WP 7.2’s admin chrome). The Quick Edit label strings dropped their emoji prefix; the plain directive names were already in the translation catalogs, so no translations were lost.
Changed
- Compatibility window: WordPress 7.0 – 7.2 (was 4.7 – 7.2), PHP 8.3 – 8.6 (was 5.6 – 8.5). The full-range PHPCompatibility scan (PHP 5.6 – 8.6) still passes clean — the code keeps its PHP 5.6-parseable syntax — but the declared and supported window is now 7.0 / 8.3 and up, per the product support policy. The runtime guards for WordPress APIs older than 7.0 remain in place as harmless no-ops and may be removed in 4.0.
- Tooling aligned with the new window:
phpcs.xmltestVersion8.3-8.6 andminimum_supported_wp_version7.0,phpstan.neonphpVersion80300,bin/preflight.shgates scanning the declared range,composer.jsonPHP constraint>=8.3, and the obsoleteWPCompat.PHPStan ignores removed (every guarded hook exists since 7.0). - Development dependencies refreshed via
composer update: PHPUnit 9.6.38, PHPStan 2.3.5, wordpress-stubs 7.1.2 (no CVEs;composer auditclean). - The “Apply Recommended Defaults” set grew from 12 to 15 contexts (button title and description updated; existing configurations are untouched — the button is opt-in).
Fixed
wp noindex-seo listwas broken on real WP-CLI installs since 3.4.0: the output call went toWP_CLIUtils::format_items()(a static method that does not exist — the real API is the namespacedWP_CLIUtilsformat_items()function), and the--contextfilter parameter collided with the--contextglobal flag reserved by WP-CLI 2.11+, so the value never reached the handler. The filter is now--only=and the command renders through the real function API. The unit-test stub was aligned with the real WP-CLI surface (namespaced function, not a Utils class), and everywp noindex-seocommand was smoke-tested against the real WP-CLI 2.12 phar.
Security
- No security changes; the new contexts reuse the consolidated settings storage and existing sanitization paths, and PHPCS/PHPStan/composer audit stay clean.
Compatibility
- WordPress: 7.0 – 7.2
- PHP: 8.3 – 8.6
Tests
- PHPUnit: 9.6.38 (198 tests, 817 assertions, all passing) on the WordPress 7.2-beta3 test library, run under PHP 8.3, 8.4, 8.5 and 8.6.0RC3 — including the icon registration, SVG rendering, emoji fallback and admin-markup tests.
- PHPCompatibility: 0 errors on the declared range 8.3-8.6 and on the full review range 5.6-8.6.
- PHPCS (WordPress-Core, WordPress-Docs, WordPress-Extra) and PHPStan level 9: 0 errors.
3.4.0
Release date: 2026-09-19
Highlights
- WP-CLI support (TODO #7): a
wp noindex-seocommand namespace with list, set, unset, export, import and bulk per-post overrides — automation-friendly and scaling past the ~200-post ceiling of the bulk UI.
Added
- WP-CLI commands.
wp noindex-seo list [--context=] [--format=table|csv|json|yaml](every context × directive pair with its status),wp noindex-seo set [...]/wp noindex-seo unset [...](default: all five directives; saving clears the transient cache),wp noindex-seo export [](JSON payload to STDOUT or a file, same as the tools tab),wp noindex-seo import [--dry-run](same validation and sanitization as the import form;--dry-runreports the number of values that would change), andwp noindex-seo override ... [--off](bulk per-post granular overrides without the UI’s selection limit;--offclears the override and directive meta). The commands enforce the same rules as the settings page: unknown contexts/directives are rejected and the attachment context refuses to enable while the implementation method is meta-only. stubs/wp-cli-stubs.php: minimal WP-CLI signatures for PHPStan and a recording stub for the test suite (never shipped; excluded from the release ZIP and from PHPCS).- The undefined “migrate-attachments” command from the original TODO was replaced by the general-purpose
overridebulk command, which covers the same need for every post type.
Changed
- Nothing.
Fixed
- Nothing.
Security
- No security changes; the CLI handlers reuse the existing validated/sanitized helpers, operator-supplied file paths are checked with
is_readable()before reading, and PHPCS/PHPStan/composer audit stay clean.
Compatibility
- WordPress: 4.7 – 7.2
- PHP: 5.6 – 8.5
Tests
- PHPUnit: 9.6.36 (185 tests, all passing): command registration, set/unset round-trips (including transient invalidation and defaults), unknown context/directive rejection, the attachment header-only invariant, list rows and filtering, export to STDOUT and file, import dry-run/apply/rejection, and override enable/disable/skip-missing.
3.3.0
Release date: 2026-09-19
Highlights
- The settings page is reorganized with the native WordPress tab navigation (General / SEO Settings / Import-Export) and titled sections, built exclusively on core
nav-tabclasses with URL-based switching — no proprietary tab CSS or JavaScript.
Added
- Native WordPress tab navigation on the settings page (
nav-tab-wrapper/nav-tab, the same pattern as the core Updates and Plugins screens): General (implementation method and configuration options), SEO Settings (the directive grid), and Import / Export (the JSON tools). Tabs switch through thetabquery parameter and the active panel renders server-side, so they work without JavaScript and are bookmarkable. - Titled sections inside the tabs: “Recommended settings” (one-click defaults), “Overview” (the number cards) and “Page contexts” (the directive grid) on SEO Settings; “Export settings” and “Import settings” headers on the Import / Export tab. Spacing added between the tab bar and the content below.
Changed
noindex_seo_process_form()is now tab-scoped: saving from the General tab updates only the configuration and preserves the directive grid, saving from SEO Settings updates only the directives and preserves the configuration — hidden tabs can no longer reset each other’s values. The form carries anoindex_seo_form_scopemarker and the redirect returns to the tab the user saved from.- The header-only restriction for the attachment context now follows the saved implementation method (the method select lives on the General tab), and switching the method to meta/HTML-only from General zeroes the attachment directives to keep the invariant consistent.
- The apply-suggestions redirect and the import success/error redirects return to their source tabs (
tab=settings,tab=tools). - Removed the dead custom tab machinery: the
.noindex-seo-tabCSS rules, theinitTabs()JavaScript with its localStorage persistence, and the unused.noindex-seo-apply-noteclass (previously flagged as dead code by the 3.1.6 deploy audit).
Fixed
- Nothing.
Security
- No security changes; the tab parameter is strictly safelisted server-side (
sanitize_key+ allow list) and PHPCS/PHPStan/composer audit stay clean.
Compatibility
- WordPress: 4.7 – 7.2
- PHP: 5.6 – 8.5
Tests
- PHPUnit: 9.6.36 (172 tests, all passing): tab markup (three tabs, active class, unknown-tab fallback), per-tab rendering isolation, scope-aware saving (config-only / grid-only / preserve-other-side / attachment invariant / tab-aware redirect), and section ordering.
3.2.0
Release date: 2026-09-18
Highlights
- Two roadmap features: JSON export/import of the settings (#6) and runtime conflict verification against the
wp_robotsAPI (#10).
Added
- Export / Import of settings (JSON). Two new controls on the settings page: “Export settings” downloads the consolidated settings as a JSON file (
noindex-seo-settings-{date}.json), and “Import settings” restores the global settings from a previously exported file. Imports are strictly validated and sanitized: plugin marker check, 1 MB size limit,is_uploaded_file()verification, the upload is never persisted to the uploads directory, every directive is normalized to 0/1, config values go through the same safelists as the settings form, and contexts that do not exist on the importing site (e.g. custom post types from another install) are dropped. Per-post and per-term granular overrides are content data and are never touched by an import. Both actions requiremanage_optionsand a nonce; success and error notices survive the redirect. - Runtime conflict verification (TODO #10). When no known SEO plugin conflicts,
noindex_seo_detect_conflicts()now also inspects the callbacks actually registered on thewp_robotsfilter (WordPress 5.7+): anything that is not a core callback or one of ours — a foreign function, a class method, or a closure from a theme or unknown plugin — produces a dismissible admin notice naming the detected callback. The core callback safelist is filterable vianoindex_seo_core_robots_callbacks. The notice honours the existing “disable compatibility warnings” opt-out.
Changed
- Nothing.
Fixed
- Nothing.
Security
- No security changes; the new endpoints follow the canonical checklist (capability + nonce on both actions, strict input validation, safelisted config values, no persistence of uploads) and PHPCS/PHPStan/composer audit stay clean.
Compatibility
- WordPress: 4.7 – 7.2
- PHP: 5.6 – 8.5
Tests
- PHPUnit: 9.6.36 (165 tests, all passing): export payload shape and JSON round-trip, import sanitizer (happy path, normalization, unknown-context dropping, malformed-payload rejection), handler guards, foreign-callback detection (functions, class methods, closures, own-prefix skipping, filterable safelist) and the runtime notice.
3.1.6
Release date: 2026-09-18
Highlights
- Admin UI modernization: all inline styles removed in favour of WordPress admin color-scheme-aware, RTL-ready classes (audit finding M7), plus the two minor items deferred from the 3.1.5 deploy audit (stale wp_die message, unreachable REST fallbacks).
Added
- Regression tests locking in the new UI conventions: the settings page and meta box render without any
style="..."attribute, and the stylesheet is verified to load on the post-list / post-edit / term-edit screens (gated by the granular flags) while staying off everything else.
Changed
- All 61 inline
style="..."attributes removed from the settings page, post meta box, term edit panel, Quick Edit, and the post/term list “Robots” columns. Styling now lives inassets/css/admin.cssunder semantic class names. - Admin styling now follows the WordPress admin color scheme: accents use
var(--wp-admin-theme-color, #2271b1), status boxes and badges use the core admin palette, and neutral grays match wp-admin. The UI adapts to Light / Blue / Midnight / Coffee / Ocean / etc. instead of hardcoded Material-Design colors. - RTL support: layout uses logical CSS properties (
margin-inline-start,border-inline-start) and the toggle switch mirrors under[dir="rtl"]. - Dynamic visibility (override toggles in the meta box and term panel) uses the core
.hiddenclass withclassList.toggle()instead of inlinedisplaymanipulation. assets/css/admin.cssalso enqueues on the admin screens where the plugin renders UI (post list, post editor, term editor), gated by the granular-control flags; the interactive JavaScript remains settings-page-only.- Admin stylesheet cleanup: duplicate
.noindex-seo-view-linkrule block removed; keyframe names prefixed to avoid collisions.
Fixed
- Stale
wp_die()message in the two form handlers: after the 3.1.5 nonce split only the capability path is reachable there, so the message is now “Permission denied.” instead of “Permission denied or invalid nonce.” (deploy-audit finding I2). Catalan and Spanish translations and the.pottemplate updated;.mofiles recompiled. - Unreachable
function_exists( 'rest_authorization_required_code' )ternaries removed from both REST permission callbacks — the function exists on every supported WordPress version (deploy-audit finding I5).
Security
- No security changes; the full canonical checklist (
AGENTS-security.md), PHPCS security sniffs andcomposer auditremain clean.
Compatibility
- WordPress: 4.7 – 7.2
- PHP: 5.6 – 8.5
Tests
- PHPUnit: 9.6.36 (150 tests, all passing). Coverage maintained above the 80% floor.
3.1.5
Release date: 2026-09-18
Highlights
- Maintenance review: composer update, code review, security review and compatibility review (WordPress 4.7 – 7.2, PHP 5.6 – 8.5). Declared WordPress floor corrected to the real tested minimum (4.7). Test suite expanded to 147 tests with 94.78% line coverage, meeting the 80% AGENTS target.
Added
- 76 new PHPUnit tests: front-end output (
noindex_seo_metarobots, all majornoindex_seo_showcontexts, overrides, CPT defaults, transient shortcut), admin surface (settings page, meta boxes, Quick Edit, bulk actions, columns, filters, enqueues, conflict detection, helpers), sitemap per-term exclusion, and uninstall cleanup.
Changed
- Minimum WordPress requirement raised from 4.0 to 4.7 in the plugin header,
readme.txtandphpcs.xml(minimum_supported_wp_version). Version 4.7 is the oldest release in the tested range and the first with the complete REST API, so every advertised feature (including the/wp-json/noindex-seo/v1/endpoints) is available on all supported versions. Newer-API integrations keep degrading gracefully above the floor (wp_robots5.7+, sitemaps 5.5+, block editor 5.0+,register_post_meta, term meta). - Tested up to WordPress 7.2 in
readme.txt. - Removed three now-obsolete
WPCompatPHPStan ignores (rest_api_initaction,WP_REST_Request::get_param()× 2): with the 4.7 floor those APIs always exist, and PHPStan reports unmatched ignores as errors. - Dev dependencies updated via
composer update(phpstan/phpstan2.2.8 → 2.2.14,nikic/php-parser5.8.0 → 5.9.0,php-stubs/wordpress-stubs6.9.4 → 7.1.0,szepeviktor/phpstan-wordpress2.0.3 → 2.0.4,wp-hooks/wordpress-core1.12.0 → 1.13.0). No production code ships these packages. - Coverage regression floor in
bin/preflight.shraised from 25% to 80% now that the suite meets the AGENTS target.
Fixed
- Dead nonce check in
noindex_seo_process_form()andnoindex_seo_process_apply_suggestions():! check_admin_referer()with a specific action can never be true (the function exits on failure instead of returning false), so the||branch was dead code flagged by PHPStan level 9 with the updated WordPress stubs. Both handlers now checkcurrent_user_can( 'manage_options' )first and callcheck_admin_referer()separately, which dies on an invalid nonce. No behavior change for valid requests.
Security
- Full canonical checklist (
AGENTS-security.md) reviewed against the release diff: no findings. Input validation (safelist + strict types), sanitization (sanitize_text_field,absint,sanitize_key), late output escaping, nonces on every state-changing action, capability checks in UI and execution logic,$wpdb->prepare()on all custom SQL, no dangerous functions, RESTpermission_callbacks intact, no secrets in code.composer auditclean. - The nonce-handler fix above is hardening only: the previous code was still safe in practice because
check_admin_referer()dies on failure.
Compatibility
- WordPress: 4.7 – 7.2
- PHP: 5.6 – 8.5
Tests
- PHP Coding Standards: 3.13.6
- WordPress Coding Standards: 3.4.1
- PHPUnit: 9.6.36 (147 tests, 569 assertions — all passing on WordPress 7.1.1 test library; staging site runs WordPress 7.2-alpha, PHP 8.5.10, MariaDB 11.8.9)
- PHPCompatibility 5.6-8.5 scan: clean (declared PHP floor 5.6 confirmed)
- PHPStan level 9: clean;
bin/preflight.sh: full PASS (coverage 94.78%, above the 80% AGENTS target and floor)
3.1.3
Release date: 2026-08-17
Highlights
- Compatibility release: WordPress 4.0+ and PHP 5.6+ supported again (declared floors now match the real compatibility of the code), and branding moved to robotstxt.software.
Added
- Classic `
output onwp_headfor WordPress versions older than 5.7, restoring the pre-wp_robotsfallback so the declared WordPress 4.0 floor is real. On WordPress 5.7+ the directives keep flowing through the corewp_robots` API. noindex_seo_get_cpt_label()helper returning a safe display label for custom post types on the settings page.
Changed
- Minimum requirements lowered back to WordPress 4.0 and PHP 5.6 in the plugin header,
readme.txt,composer.jsonandphpcs.xml, verified with a full-range PHPCompatibility 5.6-8.5 scan (clean) and the WP-Compat report. - Plugin URI and Author URI now point to robotstxt.software.
register_setting()calls use the two-argument form: the$argsarray parameter requires WordPress 4.7+, and all sanitization already happens in the admin-post form handler.
Fixed
- Potential fatal errors on older WordPress versions:
is_privacy_policy()(WP 5.2+),is_customize_preview(),get_term_meta()(WP 4.4+),register_post_meta(),register_term_meta(),wp_set_script_translations()(WP 5.0+) andWP_Screen::is_block_editor(WP 5.0+) are now guarded; the features that depend on them degrade gracefully instead of crashing. - Removed PHP 7-only syntax (strict types, scalar type hints, return type declarations, null coalescing operator) so the code parses and runs on PHP 5.6 as declared.
- Restored the repository governance files (
CLAUDE.md,.claude/settings.json) that were accidentally dropped from the repository during the 3.1.2 release cleanup. - Bundled translations (Catalan and Spanish) regenerated against the current source: 9 strings added in 3.0.2–3.1.0 are now translated, 7 fuzzy entries reviewed, 149 messages complete per locale with 0 untranslated and 0 fuzzy.
Security
- No security changes; the 5.6-8.5 PHPCompatibility scan, the dependency audit and the WordPress Coding Standards security sniffs are clean.
Compatibility
- WordPress: 4.0 – 7.1
- PHP: 5.6 – 8.5
Tests
- PHP Coding Standards: 3.13.6
- WordPress Coding Standards: 3.4.1
- PHPUnit: 9.6.36 (71 tests, 385 assertions)
3.1.2 [2026-08-10]
Audit-driven maintenance release.
Security
- Fixed the composer-audit gate in
bin/preflight.sh. The previous version grepped for “security vulnerability advisory” (singular), which never matched Composer’s actual output “security vulnerability advisories” (plural) — the y→i pluralization change silently disabled CVE detection, green-lighting every release regardless of known vulnerabilities. The gate now uses Composer’s bitmask exit code (bit 0 = security), which is immune to wording changes. - Bumped
squizlabs/php_codesnifferto ^3.13.6 andwp-coding-standards/wpcsto ^3.4.1, fixing CVE-2026-67434 (OS command injection) and CVE-2026-45293 (arbitrary code execution) in the development tooling. These are dev-only dependencies and were never shipped in the release ZIP.
Fixed
- Nested `
elements on the settings page. The "Apply Recommended Defaults" form was physically nested inside the main settings form — HTML5 forbids this, and the stray` could close the main form early, orphaning the “Save Changes” button and all directive checkboxes. The apply-suggestions form is now a standalone sibling rendered before the main form. - Meta-box “Effective Directives” reference panel now reads from the consolidated
noindex_seo_settingsoption vianoindex_seo_get_setting()instead of the stale legacy individual options that may diverge after a settings save.
Added
load_plugin_textdomain('noindex-seo')hooked oninit. The plugin ships.mofiles for Catalan and Spanish (es_ES) inlanguages/, but without this call WordPress only loaded translations fromwp-content/languages/plugins/— the bundled translations were effectively dead code.- PHPUnit coverage measurement in
bin/preflight.sh(requires Xdebug). Enforces a 25% regression floor against the current 26.64% baseline; the AGENTS target remains 80%. - `
configuration inphpunit.xmlso–coverage-text` produces a report. README.mdfor developer and contributor onboarding.tests/wp-tests-config.sample.phptemplate. The realtests/wp-tests-config.phpis now gitignored and reads from environment variables.
Changed
- Pinned all remaining wildcard
""dev dependencies to caret constraints incomposer.json(phpcsutils^1.2,php-compatibility^9.3,dealerdirect/phpcodesniffer-composer-installer^1.2). Unpinned ranges were how the CVE-affected versions were resolved. - Lowered
phpstan.neonphpVersionfrom 80500 (PHP 8.5) to 80200 (PHP 8.2) so PHPStan flags any accidental use of PHP 8.3+ syntax that would break on the development floor. - Refreshed
CLAUDE.mdto reflect 3.1.x reality (version, line count, v3 consolidated storage model, four-priority output logic, current 8-file test suite). Documented the uninstall direct-DB exception. - Updated
TODO.mdto mark shipped items (#1–#4).
Compatibility
- WordPress: 5.7 – 7.1
- PHP: 7.2 – 8.5
Tests
- 71 tests, 385 assertions — all passing. Coverage measured at 26.64% (471/1768 lines).
3.1.1 [2026-07-18]
Security
- Tightened the ACL on the two REST endpoints introduced in 3.1.0. The original release used
__return_truefor bothpermission_callbacks, which would have leaked the full noindex configuration to anyone hitting/wp-json/noindex-seo/v1/settings. Fixed callbacks: /settingsnow requiresmanage_options. The consolidated array reveals the site’s full SEO strategy in a single response, so it is admin-only./effective?post_id=Nis public only for posts withpost_status=publishand no password (the directives for those would appear in the HTML `tag of the rendered page anyway). Drafts, private posts, scheduled posts, password-protected posts, and trashed posts requireedit_post` capability for the specific post.
Compatibility
- WordPress: 5.7 – 7.1
- PHP: 7.2 – 8.5
Tests
- Added 4 tests covering the new permission model: unauthenticated rejected on
/settings, editor rejected on/settings, unauthenticated/subscriber rejected on/effectivefor drafts, public access preserved on/effectivefor published posts.
3.1.0 [2026-07-18]
Closes the three remaining HIGH-priority items from the original gap audit.
Added
- WP core XML sitemap integration. Hooks
wp_sitemaps_posts_query_args,wp_sitemaps_taxonomies_query_args, andwp_sitemaps_users_query_argsso URLs that should not be indexed drop out of/wp-sitemap.xml. Two layers of exclusion: (1) whole-type exclusion — ifsingular.noindex=1hides every post type;single.noindex=1hides all posts;page.noindex=1hides all pages;attachment.noindex=1hides all attachments;cpt_{type}.noindex=1hides that CPT;category.noindex=1/tag.noindex=1hide those taxonomies;author.noindex=1hides the entire users sitemap. (2) per-item exclusion — posts/terms with the granular_noindex_seo_override=1+_noindex_seo_noindex=1flag are excluded from their type’s sitemap. Closes the biggest SEO-quality gap (Google Search Console flags noindexed URLs in sitemaps as a coverage warning). - Per-CPT global defaults. Every public custom post type (anything public + non-built-in: WooCommerce
product, EDDdownload, LMS lesson/course, etc.) now gets its own contextcpt_{post_type}and a “Custom Post Types” section in the settings page. Previously every CPT single fell into the genericsinglebucket; now each CPT can have its own noindex / nofollow / noarchive / nosnippet / noimageindex defaults that apply globally to all of its items. - REST API exposure. Two new read-only endpoints under
/wp-json/noindex-seo/v1/: GET /settings— returns the consolidated settings array (contexts × directives + config), so headless consumers (Next.js, Gatsby, mobile apps) can read the global configuration.GET /effective?post_id=123— returns the active directive list for a specific post, computed by the same precedence used on the front-end (per-post override → per-CPT default → built-insingle/page/attachmentcontext). Includes the implementationmethodso consumers know whether to render a `tag, anX-Robots-Tag` header, or both.
Compatibility
- WordPress: 5.7 – 7.1
- PHP: 7.2 – 8.5
Tests
- Added
tests/test-sitemap.php(per-post + global-context exclusion for posts/terms/users sitemaps). - Added
tests/test-rest-api.php(settings endpoint, effective endpoint for post-meta and CPT-default sources, 404 for unknown post). - Added
tests/test-cpt-contexts.php(dynamic context registration, per-CPT settings application).
3.0.4 [2026-07-18]
Changed
- Moved the “Apply Recommended Defaults” button from above the Search Results section to immediately above the “Search options…” input box. The previous position pushed the button below the fold on smaller screens; the new position sits it next to the search field where users naturally look for global actions.
Compatibility
- WordPress: 5.7 – 7.1
- PHP: 7.2 – 8.5
3.0.3 [2026-07-18]
Added
- “Robots” column on taxonomy list tables. When per-term granular control is on, the Categories / Tags / custom taxonomy list pages now show a “Robots” column with the same directive badges (🔍 noindex, 🔗 nofollow, 💾 noarchive, 📄 nosnippet, 🖼️ noimageindex) used on the post list. Makes it possible to scan at a glance which terms have an override and which directives are active — parity with the existing post-list UX.
Compatibility
- WordPress: 5.7 – 7.1
- PHP: 7.2 – 8.5
Tests
- Added 3 tests covering the term column renderer (no override, with override, passthrough for other columns).
3.0.2 [2026-07-18]
Fixed
- Duplicate “Search Engine Visibility” panel in the block editor. The plugin previously registered both a classic PHP meta box (
add_meta_box) AND a native Gutenberg sidebar panel (PluginDocumentSettingPanelviaassets/js/editor-sidebar.js) with the same title — both showed up in the block editor sidebar as two panels with the same name. The classic meta box is now suppressed on block-editor screens (detected viaget_current_screen()->is_block_editor, which respects the Classic Editor plugin’s per-request override). Classic editor users still get the meta box.
Added
- Per-term granular control for taxonomies. Mirrors the existing per-post granular pattern: when “Enable per-term granular control” is on, every public taxonomy (
category,post_tag, and any custom public taxonomy like WooCommerce’sproduct_cat/product_tag) gets a “Search Engine Visibility” panel on the Edit Tag screen with an override checkbox and the five directive checkboxes. Term-level directives take precedence over the globalcategory/tag/post_type_archivesettings when the term has the override enabled. Backed byregister_term_meta()withshow_in_rest=trueso headless consumers can read them.
Compatibility
- WordPress: 5.7 – 7.1
- PHP: 7.2 – 8.5
Tests
- Added
tests/test-term-meta.phpcovering registration, save handler, front-end override precedence, and uninstall cleanup.
3.0.1 [2026-07-18]
Fixed
- Self-healing v3 migration. If
noindex_seo_config_versionclaims migration completed (version >= 3) but the consolidatednoindex_seo_settingsoption is somehow missing,noindex_seo_check_migration()now re-runs the migration automatically. This recovers silently from partial failures: object-cache staleness after the write, request interrupted mid-migration, a third-party plugin clearing options, or DB write errors that did not raise a PHP error. Without this guard, the front-end would fall back to all-zero defaults (site fully indexable) until someone re-saved the settings page.
Compatibility
- WordPress: 5.7 – 7.1
- PHP: 7.2 – 8.5
Tests
- Added test for the self-healing migration path.
3.0.0 [2026-07-18]
Major release — storage consolidation. Settings are now stored in a single autoloaded option (noindex_seo_settings) instead of ~110 individual options. Reduces wp_options autoload weight on every page load. Legacy options are preserved (marked non-autoloaded) for one major version to allow safe rollback to 2.x; they will be removed in 4.0.
Added
- “Apply recommended defaults” button on the settings page. One click enables all 12 contexts flagged as “Recommended” in the UI (privacy policy, date archives, pagination, search results, attachment pages, preview/customize-preview, and 404). Previously required ~60 individual checkbox toggles.
- Filterable conflict-detection list. The list of plugins that may conflict with noindex SEO is now passed through
apply_filters('noindex_seo_conflicting_plugins', ...). Third-party code can extend or replace the list without editing the plugin.
Changed
- Storage consolidated: 110 directive options (
{directive}_seo_{context}) + 5 config options (noindex_seo_config_) merged into onenoindex_seo_settingsarray. Reads go through new accessors (noindex_seo_get_settings(),noindex_seo_get_setting(),noindex_seo_get_config()). The hourly transient cache is preserved on top. - Transient invalidation is now automatic: any update to
noindex_seo_settingsclears thenoindex_seo_optionstransient via theupdate_option_noindex_seo_settingshook. Previously only the admin form save cleared it, leaving WP-CLI / REST / programmatic edits stale for up to one hour. - Migration: existing 2.x installs are migrated automatically on
plugins_loaded(idempotent, version-gated bynoindex_seo_config_version). Legacy options are kept and marked non-autoloaded so a rollback to 2.x restores the pre-3.0 state. - Uninstall retrocompatibility:
uninstall.phpdeletes both the new consolidated option and all legacy individual options (via wildcard SQL). Sites that upgrade from 2.x and later uninstall will be cleaned up completely regardless of whether migration ran.
Compatibility
- WordPress: 5.7 – 7.1
- PHP: 7.2 – 8.5
Tests
- Added PHPUnit test suite (previously the project shipped only a plugin-header smoke test). Coverage: contexts helper, settings accessors, v2→v3 migration, form save, bulk actions, meta box save, conflict-detection filter.
2.0.2 [2026-07-18]
Fixed
- Bulk action “Enable Robots Override” no longer creates duplicate
postmetarows. The previous implementation usedINSERT ... ON DUPLICATE KEY UPDATE, but WordPress’spostmetatable has no unique key on(post_id, meta_key), so theON DUPLICATE KEY UPDATEbranch never fired and every bulk run on a post that already had the meta inserted a duplicate row. The bulk handler now usesupdate_post_meta()anddelete_post_meta()exclusively. - Bulk action “Disable Robots Override” now removes the override meta entirely (previously set its value to
0, leaving a redundant row).
Changed
- Removed three dead contexts (
feed,comment_feed,robots) that were registered, migrated, and cleaned up but never evaluated at runtime and never exposed in the admin UI. The plugin now consistently registers and evaluates 22 page contexts (down from a nominal 25). Existing orphan options from earlier installs are cleaned by the uninstall routine’s wildcard delete. - Extracted
noindex_seo_get_contexts()as the single source of truth for the context list shared by settings registration, v2 migration, form processing.uninstall.phpretains its own copy (technical constraint: the main plugin file is not loaded during uninstall) with a sync warning comment. - Corrected
phpcs.xmlminimum_supported_wp_versionfrom6.6to5.7to match the plugin header. This setting was stale from before the 2.0.1 minimum-WP correction. - Updated
readme.txtdescription counts from 25 to 22 page contexts. - Bulk action performance characteristic changed: the rewrite from a single batched SQL statement to per-post
update_post_meta()/delete_post_meta()calls trades O(1) queries for O(N) queries on bulk selections, in exchange for correctness (no more duplicate rows) and proper WP meta-cache invalidation. Typical admin bulk selections (≤200 posts) are unaffected; very large selections (thousands of posts) may take noticeably longer.
Deprecated
noindex_seo_clear_transient(): was hooked toupdate_option_noindexseo, a hook that never fires becausenoindexseois the Settings API group name, not an option name. The function now emits a deprecation notice via_deprecated_function()and continues to clear thenoindex_seo_optionstransient if called directly. The transient is cleared correctly insidenoindex_seo_process_form()after every successful save. The function will be removed in a future release.
Code Quality
bin/preflight.sh: hardened temp-file handling (mktemp moved to script top,trap … EXITcleanup, no more predictable/tmp/composer-audit.*paths that were vulnerable to symlink overwrite on shared hosting); fixed2>/nulltypo; added rsync /.distignore/ build-tree-non-empty guards so the forbidden-artefact scan cannot pass vacuously; replaced npm-audit subshell withpushd/popdso gate counters are not lost.- Added
bin/preflight.sh: automated pre-deploy verification covering PHPCS, PHPStan level 9, PHPCompatibility, PHPUnit, dependency audit, and candidate-ZIP artefact inspection. Prints a PASS/FAIL report per section and exits non-zero on any failure. - Added
.claude/settings.jsondeny rules fordeploy.sh,git push,git tag, andgit merge, enforcing the agent operating boundaries mandated by AGENTS.md mechanically rather than only in prose.
Compatibility
- WordPress: 5.7 – 7.1
- PHP: 7.2 – 8.5
Tests
- PHP Coding Standards: 3.13.5
- WordPress Coding Standards: 3.3.0
- PHPStan: 2.1.55
- PHPUnit: 9.6.34
2.0.1 [2026-05-25]
Added
- New option “Delete all plugin data on uninstall” (Settings → noindex SEO → General Configuration). By default, all data is preserved when the plugin is uninstalled. Enable this option to remove all settings and per-post directives on uninstall.
Changed
- Lowered minimum WordPress requirement from 6.6 to 5.7, matching the actual oldest API used (
wp_robotsfilter, introduced in WP 5.7). Verified withjohnbillion/wp-compat. - Custom post types registered by plugins or themes on the
inithook now correctly receive the Robots column, bulk actions, and list filter in granular control mode. Previously only built-in post types (post,page,attachment) were supported.
Code Quality
- Added
NOINDEX_SEO_VERSIONconstant; all enqueued assets now use it for cache-busting. - PHP type safety: replaced
(string)casts onmixedvalues fromget_option(),get_post_meta(), and$_POST/$_GET/$_REQUESTsuperglobals with properis_string()/is_scalar()narrowing guards. Passes PHPStan level 9 on PHP 8.5 with zero errors. - Extracted
noindex_seo_save_directives_from_post()helper; meta box save and Quick Edit save now share a single implementation. - Improved
noindex_seo_contextsfilter documentation: documents thestr_replace-based directive key derivation and thenoindex_seo_{context}key format requirement. - Removed dead code:
isset($field['conditional'])block (key never existed in field shape), redundantisset($field['suggestion'])guard, redundantisset($section_icons[$section_id])guard, and a provably non-empty$meta_querycheck. - Removed dead hook registration for
update_option_noindexseo(Settings API group name, not an option name; hook never fired).
Compatibility
- WordPress: 5.7 – 7.1
- PHP: 7.2 – 8.5
Tests
- PHP Coding Standards: 3.13.5
- WordPress Coding Standards: 3.3.0
- PHPStan: 2.1.55
- PHPUnit: 9.6.34
2.0.0 [2026-01-20]
New Features
- 5 independent robots directives: noindex, nofollow, noarchive, nosnippet, noimageindex
- Each directive can be enabled independently for any page context (125 total options)
- HTTP Headers support: Choose between HTML meta tags, HTTP headers, or both
- Granular per-post/page control (optional):
- Override global settings for individual posts/pages
- Meta boxes for Classic Editor
- Native Gutenberg sidebar panel for Block Editor
- Quick Edit support for fast inline editing
- Bulk actions to enable/disable overrides
- Custom “Robots” column in post lists
- Filter posts by override status
- Modern checkbox-based interface with emoji icons and tooltips
Security & Code Quality
- Modernized code with PHP 7.2+ strict types and type declarations
Compatibility & Migration
- WordPress: 6.6 – 6.9
- PHP: 7.2 – 8.5
- Automatic migration from v1.x (your existing settings are preserved)
- No manual configuration needed – just update and go
- Fully backward compatible
1.2.0 [2025-04-08]
Changes
- Improved functions documentation.
Fixes
- The way the options are saved.
Compatibility
- WordPress: 4.1 – 6.8
- PHP: 5.6 – 8.4
Tests
- PHP Coding Standards: 3.12.1
- WordPress Coding Standards: 3.1.0
- Plugin Check (PCP): 1.4.0
1.1.1 [2024-11-04]
Added
- Configuration option to dismiss other SEO plugin incompatibilities.
Compatibility
- WordPress: 4.1 – 6.7
- PHP: 5.6 – 8.4
1.1.0 [2024-11-02]
Added
- Detects other WordPress SEO plugins, and creates a notice about it, to avoid conflicts.
- Has filters, so other plugins can hack.
Changed
- Uses native wp_robots functions (since WP 5.7+)
- Big refactory.
- Less size, improved code quality.
Compatibility
- WordPress: 4.1 – 6.7
- PHP: 5.6 – 8.4
Tests
- PHP Coding Standards: 3.10.3
- WordPress Coding Standards: 3.1.0
- Plugin Check (PCP): 1.1.0
1.0.12
- Compatibility: WordPress 4.1 – WordPress 6.4.
- Compatibility: PHP 5.6 – PHP 8.3.
- Compatibility: WordPress Coding Standards 3.0.1.
1.0.11
- Fix showing all the text.
1.0.10
- WordPress Coding Standards 3.0.0 compatible.
- Compatibility: PHP 5.6 – PHP 8.3.
1.0.9
- Improved functions checking.
- Compatibility: WordPress 5.2 – WordPress 6.3.
- Compatibility: PHP 7.0 – PHP 8.3.
1.0.8
- Compatibility: WordPress 5.2 – WordPress 6.2
- Compatibility: PHP 7.2 – PHP 8.1
1.0.7
- Minor refactoring
- Compatibility: WordPress 4.9 – WordPress 6.1
- Compatibility: PHP 5.6.20 – PHP 8.1
1.0.6
- Compatibility: WordPress 4.9 – WordPress 6.0
- Compatibility: PHP 5.6.20 – PHP 8.1
1.0.5
- Compatibility: WordPress 4.9 – WordPress 5.9
- Compatibility: PHP 5.6.20 – PHP 8.1
1.0.4
- Compatibility: WordPress 5.8
1.0.3
- Compatibility: WordPress 5.7
1.0.2
- Compatibility: WordPress 5.6
- Compatibility: PHP 8.0
1.0.1
- Compatibility: WordPress 5.5
1.0.0
- Compatibility: WordPress 5.4
0.1
- First version.
