BinaryPH AI SEO is a complete, standalone SEO plugin built for non-technical site owners. Click one button and we set up your site. Click another and we fix every page that needs attention.
What it does
- 1-Click Optimize. Fixes missing focus keywords, meta descriptions, SEO titles, image alt text, schema, and canonicals across your whole site in one go. Live progress, pause/cancel, retry on failure.
- Quick Setup with best-default config. Pick a profile (Blog / WooCommerce / News) or hit "Apply best defaults" - sitemap, schema, social tags, daily automation, robots.txt all configured for you.
- AI focus keywords + search snippets for posts, pages, and WooCommerce products.
- AI-powered image alt text using vision-capable providers (Gemini, OpenRouter vision, Pollinations, Ollama llava).
- XML sitemap. A real sitemap index that scales to large sites - posts, pages, products and every public post type, category/tag archives, and featured images as image-sitemap entries. Hidden and password-protected content is left out automatically.
- Connected schema graph. Organization (with your social profiles), WebSite, WebPage, Article, author Person (job title + profiles), BreadcrumbList, and ProfilePage on author archives - plus FAQ, Product, Recipe, Event, JobPosting and LocalBusiness.
- Open Graph and Twitter Card tags on every page type, with a default share image fallback.
- Robots meta done right. One merged robots tag through the WordPress API, with full-length snippet permissions (max-snippet:-1) for featured snippets and AI Overviews.
Built for AI answer engines (AEO / GEO)
- llms.txt - a Markdown guide to your site that some AI agents and coding tools read. Google Search ignores it, so it neither helps nor hurts rankings; it is on by default because it costs nothing.
- Markdown copies - add .md to any post URL for a clean, noindexed Markdown version that AI agents read cheaply and accurately.
- AI crawler control - allow all AI bots, keep AI search but block model training (GPTBot, ClaudeBot, Google-Extended, CCBot…), or block them all. Written to robots.txt with a Content-Signal line.
- IndexNow - instantly notify Bing (which powers ChatGPT search and Copilot), Yandex, Seznam, Naver and Yep when you publish, update or delete.
- Answer-first FAQ - show the FAQ from the SEO box on the page ([bps_faq] or automatically). Google no longer shows FAQ rich results, but the visible questions and answers are what AI assistants quote; FAQ schema is only output when the FAQ is visible.
- Author expertise - job title and social profiles on each user profile feed the author schema (E-E-A-T).
- Per-post meta box with focus keyword, SEO title, search snippet, canonical, "Hide from Google" toggle, and a live SERP preview.
- 301 redirects + 404 logger. When pages move, redirect them. We log 404s so you can fix them with one click.
- robots.txt editor. No file editing required.
- SEO Health Score (0-100) on the dashboard so you always know where you stand.
- Audit log. Every change tracked, with old → new diffs.
- Import from Rank Math, Yoast, AIOSEO. One-click migration of focus keywords, snippets, SEO titles, canonicals, noindex flags, redirects, and 404 logs. Tools → Import.
- Cost tracker + monthly budget cap for paid AI providers.
- AI load balancer + failover. Enable several providers and calls are spread across them (prefer-free-first, round-robin, or fixed priority), failing over automatically when one errors or rate-limits.
- Web-search context. Providers with native search (Gemini grounding, Groq compound, OpenRouter web plugin) can ground generations in live results; everyone else can use your Brave / Google PSE / custom Mistral-agent search pool.
AI providers supported
Designed for everyone
- Plain-language labels everywhere ("Search snippet", "Hide from Google") with the technical name in tooltips.
- Beginner mode by default - power-user fields stay hidden until you ask for them.
- Dark mode supported. Responsive admin pages.
- Fully translatable.
- Encrypted API keys at rest (libsodium).
- Works alongside Yoast, Rank Math, and All in One SEO if you want - or replace them.
How it pairs with Yoast / Rank Math / AIOSEO
By default, fresh installs run standalone. If we detect Yoast or Rank Math when you activate, we switch to "defer" mode and fill in their fields instead of duplicating. Pick your preferred mode in Settings → Advanced.
3.1.1
Cleaner indexing reports, correct canonicals for block-theme post lists.
- New: feeds (the site feed, each post's comment feed, category and tag feeds) send X-Robots-Tag: noindex, follow, so they stop piling up under "Crawled - currently not indexed" in Search Console. Feed readers are unaffected. Skipped when another plugin already set robots rules for the feed; turn it off with the bps_noindex_feeds filter.
- Fix: on block themes, a Query Loop that paginates on its own (for example a "latest posts" grid with ?query-3-page=2) now keeps its page number in the canonical and og:url. Before, page 2 and later pointed back to page 1 although they list different posts. Loops that follow the main pagination are unchanged.
- Fix: when a static llms.txt in the site root is served instead of ours (for example by Hostinger's LLM Optimization or another plugin), the plugin no longer adds a second link to it and says so under Settings and Tools.
- Tested: every "Force SEO data target" choice and coexistence mode against Yoast SEO 28.7, Rank Math 1.0.280 and All in One SEO 5.0.3: one set of SEO tags per page, and values written through BinaryPH show up in whichever plugin renders the page.
3.1.0
Honest scoring, cleaner schema and hidden API keys.
- Change: the SEO Health Score now leaves out checks that have nothing to measure yet (for example no images in the library, or no post long enough to score readability). They show as "Not measured" in the breakdown instead of counting as a pass, so the score can go down or up after updating: it now covers only what was measured.
- Fix: the robots.txt part of the score was 0 on sites using the default robots.txt, which is valid and names the sitemap. It now passes whenever search engines may crawl the site and robots.txt lists the sitemap.
- Fix: the sitemap index's last-modified date could come from a hidden (noindexed) or password-protected post, which is not in the sitemap. It now uses only posts the sitemap lists.
- Fix: AI-written and automatic image alt text is capped at 125 characters, cut at a word.
- Fix: LocalBusiness location markup now has its own @id, is linked to your Organization, and no longer outputs empty address fields.
- Fix: the setup wizard page logged a PHP deprecation on every visit, and opening it directly did not clear the first-run redirect.
- Fix: the admin body class was registered as an action instead of a filter; term descriptions and author names were looked up with objects or strings where WordPress expects IDs.
- Fix: AI output trimming could cut the last byte off a non-Latin character.
- New: posts with a Markdown copy also announce it in an HTTP Link header (rel="alternate"; type="text/markdown"), so AI agents find it without parsing the page.
- New: custom JSON-LD in the SEO box is checked when you save: missing @context or @type, types that no longer earn a Google rich result (HowTo, SpecialAnnouncement, CourseInfo, EstimatedSalary, LearningVideo, ClaimReview, VehicleListing, and FAQ rich results) and leftover placeholders like [Business Name] are listed. The markup is still saved.
- New: API key fields have a Show key / Hide key toggle. Keys always start hidden.
- Change: admin scripts and styles now carry a content hash in their file names, so an update never serves a stale cached copy.
- Change: settings and readme text no longer suggest llms.txt helps rankings (Google ignores it; some AI tools read it).
- Developer: the plugin passes phpstan level 6, strict TypeScript checks on its admin JavaScript, phpcs with the WordPress and PHP 8.0 compatibility standards, Semgrep and Gitleaks, and a WordPress smoke test on PHP 8.5.
3.0.0
SEO for the AI era - sitemap fix, answer-engine features, and a full correctness audit.
- Fix: every sitemap URL (/sitemap.xml and each child sitemap) was 301-redirected to a trailing-slash variant on sites whose permalinks end in "/" - Search Console could report "Success" with 0 discovered pages. Sitemaps now answer 200 directly (the slash form is accepted too) and send X-Robots-Tag: noindex.
- New: taxonomy sitemaps (categories, tags, product categories - empty and hidden terms skipped), featured images as image-sitemap entries, the homepage entry for blog-front sites, every public post type included, and a readable browser view (/sitemap.xsl).
- New: AI Visibility settings tab - AI crawler policy for robots.txt (allow all / block training only / block all, with a Content-Signal line), IndexNow instant indexing (opt-in), and Markdown copies of every post (append .md) for AI agents.
- New: llms.txt rebuilt to the current llmstxt.org shape - sections per post type, topic map, Optional section, SEO descriptions, links to Markdown copies; noindexed and password-protected content excluded; HTML entities decoded.
- New: connected schema graph - WebSite, WebPage, primary ImageObject, author Person (with job title and social profiles from the user profile), ProfilePage on author archives, Organization sameAs from a new Social profiles setting.
- New: robots meta through the WordPress wp_robots API - one merged tag (previously noindexed pages printed two conflicting robots tags) with max-snippet:-1 / max-video-preview:-1 on indexable pages.
- New: Open Graph/Twitter on author, post-type and term archives; og:locale, article times and section, image dimensions, twitter:site from your X profile, and a default share image fallback. Self-referencing canonicals on paginated archives and the paged homepage.
- New: [bps_faq] shortcode and an option to show the SEO-box FAQ on the page. FAQ and HowTo schema are now only emitted when that content is visible (markup for invisible content violates Google's structured-data policy). HowTo and Course default to off on new installs (Google retired those rich results).
- Fix: SEO titles and descriptions imported from Yoast (%%title%%), Rank Math (%title%) and AIOSEO (#post_title) printed their template variables literally - they are now resolved, including on already-imported data.
- Fix: a static front page's own SEO title and snippet are now used on the homepage.
- Fix: "Strip category base" could turn every page, post, robots.txt and sitemap URL into a 404 after a rewrite flush (catch-all category rule). It now registers explicit per-category rules and 301-redirects old /category/ links.
- Fix: robots.txt no longer advertises the sitemap of a site set to "Discourage search engines", and the sitemap line now follows site-URL changes ({sitemap} placeholder).
- Fix: cross-domain redirects (same path on a new domain) were blocked by the self-loop guard; percent-encoded redirect URLs (non-ASCII slugs) were corrupted on save; mixed-case target hosts were rejected.
- Fix: importers - Rank Math redirections with several sources imported only the first, 410/451 rules were dropped, CSV files with a UTF-8 BOM imported their header row as a redirect.
- Fix: automation candidate queries counted disabled fields, so posts missing only a disabled field could block the queue; titles with HTML entities leaked into generated keywords and titles.
- Fix: the monthly budget cap undercounted Gemini spend (outdated prices, thinking tokens and search grounding ignored). Default Gemini model is now gemini-flash-latest and Ollama's is gemma3; reasoning models' think blocks are stripped from replies.
- Fix: dry-run mode no longer swallows manual edits in the SEO box; editors without the SEO capability no longer see AI buttons that could only fail; plugin error notices are shown only to SEO managers.
- Improved: block editor - SEO fields registered in the REST API, the snippet preview reads the block editor's title, and suggested slugs apply through the editor. The SEO box now appears on every public post type.
- Improved: PHP 8.4/8.5 compatibility (explicit CSV escape arguments), an upgrade lock against concurrent upgrade runs, and many smaller fixes (verification-tag paste, JSON-LD field sanitizing, title capitalization, WooCommerce list columns, Rank Math/AIOSEO secondary keyphrases preserved, localized image alt text).
2.9.0
Convergence release: scheduled automation now actually finishes the backlog, the audit log becomes a real tool, and a broad production-hardening pass.
- New: catch-up mode (on by default) - when a run finishes with work left over, the plugin schedules another run ~20 minutes later until the backlog is gone, bounded by a new "Daily AI-call limit" (default 50, Settings → Automation). Previously one capped daily run could be permanently outpaced by a site that publishes ~2 posts/day, so the "items need attention" count only ever grew.
- New: catch-up runs process oldest content first, so the old backlog can no longer be starved forever by a steady stream of new posts.
- New: the dashboard tells the truth about throughput - a forecast line ("clears in ~X days" / "will never clear at this budget"), a Missing SEO title card (it was always counted in the headline but had no card, so the cards never summed to the total), and a catch-up progress line under Last automation run.
- New: audit log overhaul - server-side pagination with per-page picker, filters by field and source, search by post title, relative timestamps, a per-provider AI usage breakdown, and a Clear log button (everything / older than 30 / 90 days; admins only). Retention is now configurable (Settings → Advanced) and old entries prune automatically.
- Fix: log pruning (audit, AI calls, 404s) ran only when scheduled automation was enabled - with the schedule off, all three tables grew forever. Pruning now runs on its own daily maintenance event, always.
- Fix: the 404 "Clean noise" sweep classified stored rows against the CURRENT request's query string - a request carrying e.g. ?author=1 could mark every logged 404 as bot noise and delete the whole log. Stored rows are now judged only on their own URL.
- Fix: redirect lookups no longer cost every front-end pageview 1-2 uncached queries on sites with no redirects (cached rule counts short-circuit), and the exact-match lookup now uses the source index instead of a full-table BINARY scan.
- Fix: the Redirects page now follows POST-redirect-GET - refreshing after adding or dismissing no longer re-submits the form.
- Fix: paginated sitemaps advertised the site-wide last-modified date on every page; now only page 1 carries lastmod, and the sitemap index and /llms.txt are cached instead of rebuilt on every crawler hit.
- Fix: editor autosaves no longer wipe the score/orphan/content caches on every keystroke (autosave/revision guards on all save_post handlers).
- Improved: "Optimize my whole site" with AI mode now reports an honest plan ("~N calls needed at up to M/day - done in ~X days") instead of a vague toast; wording around AI calls vs items corrected throughout.
- Improved: i18n pass - the redirects page script moved from inline PHP to a proper translatable asset, REST error messages are translatable, and the plugin now ships license/domain-path headers per directory guidelines. Removed six dead legacy admin stubs.
2.8.2
Reliability release: fixes the Groq "messages[0].content must be a string" error, opaque OpenRouter failures, and the ways scheduled automation could silently stop making progress.
- Fix: image alt calls no longer send image payloads to text-only models - the vision check now fails closed on unknown models (fetching the provider's model list on demand) instead of assuming capability, and Groq's qwen3 models are no longer mistaken for vision models. This is what produced "groq error: messages[0].content must be a string" on every scheduled run.
- Fix: posts flagged "needs manual attention" during provider outages were permanently excluded from automation while still counted as missing on the dashboard. Strikes now only count when a provider actually answered, the flag self-expires after 30 days, filling a field through the plugin clears it, and the upgrade wipes all previously mis-flagged posts so the next run retries them.
- Fix: orphan pages whose auto-link attempt found or wired nothing were parked as "done" for a week; unproductive attempts now retry daily. The dashboard also warns when orphan fixing is on but auto-link is off (or paused by dry-run) - the state where runs cache suggestions forever without ever dropping the count.
- Fix: AI features that expect JSON (FAQ extraction, AI readability, AI orphan suggestions) were broken on most models - the response cleaner reduced multi-line JSON replies to a single brace, which was then cached as a success.
- Fix: provider error notices no longer appear when a later provider in the failover chain succeeded; genuine whole-chain failures now show one notice with the real per-provider reasons (including OpenRouter's upstream provider name and error body, previously discarded as "Provider returned error").
- Fix: a provider returning HTTP 200 with an empty completion never entered failover cooldown and kept winning the first slot of every call; deterministic client errors (400/401/404) now cool down for 30 minutes instead of retrying every 5.
- Fix: failed image downloads no longer silently degrade a vision request into a text-only "describe this image" prompt - the invented description used to be written as real alt text. Downloads are also capped at 5 MB, verified to be images, use the "large" rendition instead of the multi-MB original, and are fetched once per failover chain instead of once per provider.
- Fix: slow provider timeouts inside the post loop could spend the whole run's wall clock so the media alt sweep and orphan pass never executed; AI calls now honor the run deadline and the post loop is capped at ~65% of the time budget when later passes have work.
- Fix: cache hits and skipped calls no longer consume the per-run AI call budget; snippet/title/alt failures now count strikes like keywords did, instead of re-burning budget on the same unfillable post forever.
- Improved: a dashboard warning now appears when AI features are enabled but scheduled automation is off (the state where the AI toggle said "On - every feature uses AI on the daily cron" while the cron never ran), and when no provider is enabled and configured the run downgrades to heuristic with a clear notice instead of failing silently.
2.8.1
Cerebras vision fix.
- Fix: image alt-text requests routed to Cerebras failed with "Remote image URLs are not supported" - images sent to Cerebras are now downloaded and inlined as base64 data URIs.
- Fix: image Content-Type headers with parameters (e.g.
; charset=...) are stripped before building data URIs and Gemini inline payloads, which would otherwise be rejected as invalid.
2.8.0
Multi-provider release: AI load balancer + failover, web-search grounding, and automation reliability fixes.
- New: Groq, Cerebras, and Mistral AI providers (OpenAI-compatible chat completions, free tiers supported).
- New: AI load balancer + failover - enable any set of providers on the new Settings → AI & Search tab, order them, and pick a strategy (prefer free first / round-robin / fixed priority). Failing providers go on a short cooldown and are retried last; 429 responses honor Retry-After.
- New: per-provider model pickers - fetch each provider's live model list (free models sorted first and labeled "(free)"), or type any model id manually. Per-provider models replace the single global model that previously only applied to the primary provider.
- New: web-search context for text generation (keyword, snippet, SEO title - never image alt). Native search where the provider supports it: Gemini Google-Search grounding, Groq compound systems, OpenRouter web plugin. External search everywhere else via a failover pool of Brave Search, Google PSE (existing customers; Google retires the API on 2027-01-01 - the UI says so), or your own Mistral Studio web-search agent (fetched from your account; CSE-shaped JSON agents parse natively, citation output works as fallback). Per-provider search toggle, per-engine daily usage counters.
- Fix: the daily automation's last-run stats can no longer go stale - the run records progress incrementally, a wall-clock guard stops work before host timeouts kill the process, and an interrupted run is labeled on the dashboard instead of silently showing four-day-old numbers.
- Fix: the media-library alt sweep and the orphan pass now get guaranteed slices of the per-run AI budget - previously the post loop could consume everything and the sweep was skipped entirely, which is why "missing image alt" counts never converged in AI mode.
- Fix: posts whose titles are empty or all stopwords (nothing can be generated from them) are flagged "needs manual attention" after 3 attempts and skipped, instead of silently re-burning AI calls every run forever; the dashboard shows the count and editing the post retries it.
- Improved: dashboard shows next-run time and an overdue warning (WP-Cron on low-traffic sites), updated model defaults (Gemini 2.5 Flash; xAI grok-4.3 - grok-3 was retired), Settings help text now explains the AI-call budget honestly, and the settings/tools tab bar highlights the active tab again.
- Improved: admin UI responsive pass - provider/engine cards stack on phones and in-app webviews, 44px touch targets on reorder controls, wide tables scroll sideways instead of blowing out the page.
2.7.1
Plugin Check compliance fixes for the 2.7.0 release.
- Fix: dry-run staged-changes queries now pass LIKE wildcards through
$wpdb->esc_like() + prepared placeholders instead of interpolating them into the SQL.
- Fix: translator comments added to all translatable strings with placeholders, and confirmation-dialog strings no longer wrap their text in HTML (the markup moved to the JS layer).
- Fix: normalized mixed line endings in
class-bps-assets.php introduced by the 2.7.0 i18n merge.
2.7.0
Install-and-forget release: a full audit of the four dashboard features (missing keyword / snippet / image alt / orphan pages), the automation pipeline, and the admin UI.
- Fix: every AI generation path now falls back to the built-in heuristic in the same run when the provider returns nothing (bad key, network failure, empty response, budget cap). An unattended site with a broken provider keeps making progress instead of stalling forever - this covers the daily cron (keyword, snippet, SEO title, image alt, orphan suggestions), the inline optimizer, the per-post Generate buttons, and the FAQ/slug/internal-link helpers. The dashboard's "Last automation run" card reports how many AI calls fell back.
- Fix: "Missing image alt" can now actually reach zero unattended - the daily automation sweeps the whole media library (previously only featured images), AI mode really calls the vision provider on featured images, and stored alt text is injected into in-content
<img> tags at render time.
- Fix: orphan detection counts navigation-menu links as inbound (menu pages were false-positive orphans), no longer counts a post's link to itself, and caches URL lookups so rescans are fast. AI link suggestions validate returned post IDs against the real candidate list. Orphan suggestion caching now spans a week so daily runs work through the whole backlog instead of re-processing the same head of the list.
- Fix: the Optimize picker no longer saves "Skip" for features that merely had nothing to fix that day - a transient zero-count used to permanently disable that feature's automation for future content. The lazily-loaded orphan count now reaches the picker, the AI toggle no longer silently reverts or overwrites per-feature Skip choices, and a mid-run connection hiccup retries instead of stranding the run.
- Fix: the daily run fires at the configured site-local time (was 03:00 UTC on every non-UTC site). The cron self-heals if its event vanishes, a warning appears when WP-Cron is stalled or overdue, presets and settings-import reschedule the cron when they change frequency, and the Quick Setup no longer clobbers a just-picked profile.
- New: dry-run mode is now reviewable - staged values are listed in Settings → Advanced with Apply / Discard buttons, count as "done" so the cron stops re-billing the same posts daily, and orphan auto-link (the one path that still changed the live site) is paused while dry-run is on.
- New: first heuristic fills happen within ~10 minutes of activation (previously waited for the first 03:00 run); Settings → "Run now" executes in the background instead of timing out the page.
- New: the setup wizard asks the two questions only the user can answer - orphan auto-link consent (it renders a Related: block on the public site) and the optional AI key - everything else runs on safe defaults.
- Improved: AI output is length-clamped at word boundaries (snippets/titles), heuristic snippets decode entities and cut on word boundaries, Regenerate bypasses the response cache so a second click gives a fresh suggestion, and saved API keys can be removed from the UI.
- Improved: Content page gained working pagination, an "All content" type filter matching the dashboard card counts, and row Generate now fills the snippet as well as the keyword.
- Improved accessibility: modal focus trap + labelled dialogs, accessible names on toggle switches and help tips, correct nav semantics on tab bars, and ~120 admin JS strings are now translatable.
- Improved security: per-post capability checks on media alt fill and orphan auto-link writes, sanitized redirect updates, budget-gated + rate-limited provider test calls.
- Performance: bounded candidate queries, cached 404-rate and readability score components (readability is now a real Flesch sample instead of a hard-coded 100%), scan cache survives editor autosaves, and plugin CSS no longer bleeds into other admin screens.
2.6.8
- Fix: redirects to external domains actually arrive now.
wp_safe_redirect() silently rewrote any cross-domain target to wp-admin (sending visitors to the login screen, and letting browsers cache that as the 301). The rule's saved target host is whitelisted at redirect time while keeping scheme validation.
- Fix: Find & Replace regex mode matched nothing. The SQL pre-filter searched for the literal pattern text (
colou?r, \d{4}), so no candidate rows ever reached the regex engine. Regex mode now scans rows in batches and lets the regex decide; an invalid pattern also reports its error in the result panel instead of a silent "0 rows touched".
- Fix: the dashboard SEO Health Score ring never rendered - the inline boot script ran before footer-enqueued core.js defined
window.BPS. It now defers to DOMContentLoaded.
- Fix: save/error notices ("Saved.", "Redirect added.", "Could not add redirect…", "404 log cleared.") were queued but never displayed - custom admin pages don't auto-print settings errors. They now render above every plugin page header, so a failed redirect add is no longer silent.
- Fix: pausing 1-Click Optimize was a dead end - nothing ever called the resume endpoint. The Pause button now toggles to Resume and restarts the run; both labels are translatable.
- Fix: regex redirect rows imported via CSV using the documented
~...~ shorthand (including the placeholder example) were stored as exact matches that could never fire. The wrapper is now auto-detected when no match_type column is present.
- Fix: heuristic internal-link suggestions returned wp-admin edit URLs; the metabox "Copy link" button pasted those into post content (broken, login-walled links for visitors). Suggestions now carry the public permalink, matching AI mode.
- Fix: the auto-redirect created on slug rename assumed a flat
/%postname%/ permalink structure - dead redirects for child pages and date/category permalinks. Both sides are now derived from the real permalinks.
- Fix: sitemap
<lastmod> and Article datePublished/dateModified carried the site's UTC offset applied to an already-UTC timestamp (wrong by up to ±14 hours). Now emitted as true UTC.
- Fix: Media → bulk alt text now honors Dry-run mode (stages to shadow meta like the optimizer/cron instead of writing live), and the completion toast no longer claims success when items could not be processed.
- Fix: Event schema could emit
"image": false when the post had no featured image (Google rejects it) - false values are now dropped, matching the Recipe emitter.
- Fix: duplicate Product JSON-LD on WooCommerce product pages - WooCommerce core already emits one; ours now stands down (filter
bps_schema_product_skip_when_wc to override).
- Fix: an API key that can no longer be decrypted (salt rotation, migrated DB) was sent to the provider as a literal
bps_enc_v1: ciphertext Bearer token, producing a misleading "provider rejected your key" error. It's now treated as missing and the existing "re-enter your keys" notice is raised.
- Fix: uninstall now removes
_bps_* term meta (term-level SEO fields previously survived uninstall) and clears the pending one-shot optimizer cron event on deactivate/uninstall.
- Fix: AIOSEO migration preview counted redirect rows with NULL
enabled but the import skipped them - both paths now agree.
- Fix: the "Could not add redirect" error message now correctly describes the actual constraints (unique source across all match types; homepage/system paths blocked).
- Tools: tabs whose endpoints require administrator rights (Import, Redirects CSV, Find & Replace, Settings export/import) are hidden from users who only hold the
bps_manage_seo capability - their buttons could only ever return 403.
- Accessibility/contrast: post-list SEO score pills (white on amber was 2.2:1) and the dashboard-widget score (CSS
orange on white was 2:1) now use darker token-aligned colors; form placeholders darkened from 2.6:1 to 4.8:1; success-state text nudged to green-800 (the soft-background pairing was a hair under 4.5:1); explicit ::selection colors added so highlighted text stays readable - including the llms.txt code preview, where selected text was effectively invisible (1.2:1).
- Fix: a stylesheet that doesn't ship with the plugin was enqueued on every post-edit screen, producing a 404 request per page load - now existence-guarded like the other page assets.
- Fix: importing redirects from Rank Math/AIOSEO could write a homepage (
/) redirect rule directly into the table, bypassing the homepage-lockout guard every other entry point honors. Such rows (and rows with no target) are now skipped and counted.
- Fix: the "has provider" check (dashboard AI toggles) no longer counts an API key that can't be decrypted as a configured provider.
- Hardening: optimizer preview modal now HTML-escapes post titles; decrypt failures consistently flag the undecryptable-keys admin notice; the external-redirect host whitelist is derived from the rule as stored, so a regex target built from back-references can't be steered to a visitor-controlled domain.
2.6.7
- Compatibility: tested up to WordPress 7.0. Replaced the deprecated
add_submenu_page(null, …) pattern (hidden Setup wizard + legacy redirect routes) with an empty-string parent, eliminating the PHP 8.1+ "passing null to non-nullable" notices WordPress core emits for a null parent slug. Hidden-page routing is unchanged.
- Fix: duplicate
<link rel="canonical"> on every post/page in Standalone mode. WordPress core's own rel_canonical was left registered alongside ours, so each singular page emitted two canonicals (which disagreed whenever a custom canonical was set). Core's is now removed whenever BinaryPH is the active SEO source - exactly one canonical is output.
- Fix: term-archive SEO (description, canonical, robots, title) is now gated by the same coexistence check as the rest of our front-end output. After migrating Yoast/Rank Math term SEO into BinaryPH term meta while still in Defer/Supplement mode, category/tag/custom-taxonomy archives no longer emitted a second set of tags on top of the third-party plugin's.
- Fix: Recipe schema emitted
"image": false (and empty description/author) when a recipe post had no featured image - Google rejects that. Empty/false values are now dropped, matching the Event/Course/Product/JobPosting emitters.
- Fix: Article schema referenced an Organization node (
publisher) that wasn't output when "organization info for Google" was disabled, leaving a dangling reference Google flags. The Organization node is now always present when an Article is emitted.
- Fix: a regex redirect whose target re-matches its own pattern (e.g. source
~^/p/(.*)$~, target /p/$1-x) caused an infinite redirect loop. Same-site regex targets that still match the rule are now refused instead of looping.
- Fix: the 404 noise filter missed backup/secret-file probes such as
/backup.zip, /db.sql, and /.htaccess because the logged path carried a trailing slash, defeating the $-anchored extension patterns. Both the raw and trimmed path are now tested, so scanner noise stops polluting the 404 log and SEO score.
- Fix: custom post type XML sub-sitemaps whose slug contains a hyphen, underscore, or digit (e.g.
product_variation, my-cpt) were advertised in the sitemap index but returned an HTML 404 because the rewrite rule only captured [a-z]+. The capture now allows [a-z0-9_-]+ (still validated against the registered types).
- Fix: 1-Click Optimize no longer re-calls the AI provider for orphan-page suggestions it already cached when Orphan auto-link is toggled on. The AI's existing 3-5 ranked picks are reused (only the heuristic pool needed the wider re-fetch), avoiding redundant token spend; the reused cache is marked so the orphan stops being re-queued.
- Fix: per-post "Hide from Google" now emits
noindex,follow instead of noindex,nofollow, matching the term/archive output and preserving the page's outbound link equity.
- Fix:
seo_title is now consistently present in the optimizer feature map across settings import, the legacy 2.2.7 upgrade migration, and the cron/asset fallback maps (it was silently dropped on those paths, defaulting SEO-title automation to off). Settings import also no longer silently re-enables Orphan pages.
- Content page: the per-row Generate / Apply buttons are hardened against clicks landing on inner nodes, and the table/pagination lookups are null-guarded.
- Sitemap page: the "Open settings" button now scrolls to the XML-sitemap toggle (the target anchor previously had no matching element).
- Coexistence: the duplicate-tag fail-safe now also recognizes SEO plugins BinaryPH has no adapter for - SEOPress, The SEO Framework, Slim SEO, SmartCrawl (plus a
bps_detected_foreign_seo_plugin filter to register others). Running one of those in Standalone mode previously slipped past detection and produced duplicate canonical/OG/schema tags.
- Coexistence: BinaryPH no longer serves its own XML sitemap when a third-party SEO plugin is active in Standalone/Override mode - it defers to that plugin's sitemap. Fixes a collision with All in One SEO, which also serves /sitemap.xml.
- Coexistence: the duplicate-tag admin notice is now mode-aware (Override mode shows an informational "this is expected" message instead of an error, names the detected plugin even when it's one without an adapter, and mentions the sitemap). Corrected the misleading
primary() doc-comment that claimed Override "ignores other SEO plugins" - Override in fact writes your values into every active SEO plugin's fields ("write everywhere") and lets that plugin render them.
- Housekeeping: full PHP 8.3 lint pass on every touched file.
2.6.6
- New: Apply button on every orphan-pages suggestion (Tools → Orphans). Click "Find similar pages" or "AI suggestions" and each row now has its own Apply action that wires the source-post → orphan auto-link and (on first use) enables the global Orphan auto-link feature. No more guessing whether suggestions were already wired or not.
- New: status banner above each suggestion list - clearly states ✓ Auto-linked from N source post(s) when the server already wired the link during the call, or Auto-link is off - click Apply to enable & wire when the toggle is disabled. The orphan row pill also flips to ✓ wired N so the table reflects state without expanding the row.
- New: Copy-link button on every AI internal-link suggestion in the post metabox. Click it and
<a href="…">anchor</a> lands on your clipboard, ready to paste wherever the anchor text fits naturally in the post body. Editor-agnostic - works for Gutenberg, Classic Editor, and page builders.
- New REST endpoint:
POST /bps/v1/orphans/wire-one - wires a single source → orphan auto-link from the Tools page Apply button. Caps + capability checks honored.
2.6.5
- Fix: AI suggestions for orphan pages (both the per-row "AI suggestions" button and the "AI suggestions for all" bulk action) now actually call the configured provider. The helper class was dispatching to a function that never existed (
bps_call_ai_provider), so every AI-driven helper - orphan suggestions, AI FAQ extraction, AI slug optimizer, AI readability - silently returned empty. Now routed through the canonical bps_call_ai() gateway.
- Fix: scheduled automation now correctly fills orphan-page AI suggestions when "Orphan pages" is set to AI mode (same underlying helper fix - the cron worker was hitting the same broken code path).
- Fix: AI FAQ extraction and AI readability scoring were silently truncating post content to 200 characters before sending to the provider (the helper passed a
$max = 4000 argument that the underlying bps_strip_for_prompt() ignored). bps_strip_for_prompt() now honors the optional length argument, so AI helpers get the intended 4000-character context.
- Fix: AI slug optimizer was always sending an empty focus-keyword in its prompt because it called a non-existent
bps_get_focus_kw() helper. Now reads the focus keyword via the same path the heuristic version uses.
- Fix: overlapping text/toggle in Settings → Schema, Tracking & Verification, and Links. A legacy 36×20px
.bps-switch rule (used by the per-row Active toggle on the Redirects page) was crushing the v2.1 switch component (which carries inline text), causing the label to overlap the track.
2.6.4
- Performance: Eliminated an N+1 database query storm in the Orphan Detector by fetching post content in bulk and caching URL resolutions.
- Fix: Resolved an infinite retry loop in the 1-Click Optimizer where unfixable orphan pages would permanently stall the queue.
- Fix: Protected core WordPress system paths (e.g. /wp-admin/, /wp-login.php, /wp-json/) from being accidentally redirected or logged as 404 noise.
- Fix: Corrected an undefined variable warning in the background cron worker.
- Enhancement: Explicit UI toggles added for granular Settings → Automation control.
2.1.1
- Migration: Rank Math and Yoast term-level SEO (focus keyword, search snippet, SEO title, canonical, noindex on every category/tag/custom taxonomy term) is now copied into BinaryPH's term meta. Previously term archives reverted to defaults after switching to Standalone.
- Migration: per-post primary category is now copied (Rank Math
rank_math_primary_category, Yoast _yoast_wpseo_primary_category → _bps_primary_category).
- Schema: breadcrumb category selection now honors the user's primary category instead of always picking the first attached category. Applies to both posts and WooCommerce products.
- Migration preview now lists detected per-post schema customizations from Rank Math (FAQ, HowTo, Recipe, Event, Course, JobPosting, Product) and Yoast (Article-type / Page-type overrides) so users see before importing what will need to be re-entered manually after switching to Standalone. We deliberately do not auto-copy these - the source plugins' serialized schema field shapes vary across versions and a bad mapping would corrupt rich snippets.
- New: when in Standalone mode and we detect leftover SEO data from Rank Math / Yoast / All in One SEO that has not been migrated, a friendly dismissible admin notice now nudges the user to run Tools → Import. Catches the common foot-gun where someone switches to Standalone before running the importer.
- Fix: a bad
/sitemap-{unknown-type}-N.xml URL no longer returns an HTML body under an application/xml Content-Type header. We now validate the post-type slug before setting the XML header.
- Security: removed JSON_UNESCAPED_SLASHES from JSON-LD output so a stray "" inside any user-supplied custom JSON-LD value can no longer break out of the script tag.
- Security: optimizer now checks the per-post
edit_post capability before rewriting any post's SEO fields - a user with bps_manage_seo can no longer touch posts they cannot otherwise edit. The bulk-apply REST endpoint now whitelists the field parameter against focus_kw / meta_desc / seo_title / canonical.
- Fix: cron lock now uses an atomic option insert instead of read-then-write, so two concurrent cron workers can no longer both pass the check and run (which previously could double-bill an AI provider). Stale locks (>5 min) are auto-cleared.
- Fix: Quick Setup re-run no longer silently overwrites user customisations. First run still applies all defaults; subsequent runs only fill in genuinely missing options. An explicit "force reset" path is available for a deliberate reset action.
- Fix: provider fallback chain no longer breaks when a custom AI model name is set. The
bps_ai_model setting now applies only to the primary provider; fallback providers use their own default model.
- Fix: cancelling the optimizer no longer triggers the "Done!" toast + confetti + page reload as if the run had succeeded.
- Fix: settings JSON import now coerces each option to its expected type (booleans → '1'/'0', integers, URLs, textareas, sanitize_key, sanitize_text_field). Type-mismatched values are skipped instead of corrupting the option.
- Fix: when ciphertext exists but libsodium is no longer available on the server, a distinct red admin notice now tells the user to re-enter their API keys, instead of the misleading "no API key configured" error.
- Fix: when no custom robots.txt is set, the plugin now actually appends the
Sitemap: line to whatever WordPress core produced (previously a dead if block did nothing).
- Fix: switching coexistence mode (e.g. Defer → Standalone after a Rank Math import) now flushes WordPress rewrite rules automatically so
/sitemap.xml routes immediately. Previously users had to visit Permalinks → Save to force a flush before our sitemap was reachable.
- Codebase: full Plugin Check / WPCS sweep - 59/59 PHP and 9/9 JS files lint-clean with documented suppressions for safe-by-construction patterns (custom-table queries, registered-prefix names, etc.).
2.1.0
- Schema expansion: FAQ, HowTo, Product (auto-detected on WooCommerce), Recipe, Event, Course, Job Posting, plus per-post Custom JSON-LD pass-through and a structured multi-location LocalBusiness editor.
- Term-level SEO on every public taxonomy (focus keyword, SEO title, meta description, canonical, noindex).
- New AI helpers - all run on YOUR own provider account: FAQ extraction from content, slug optimizer, readability scorer (Flesch + AI tips), internal-link suggestions.
- Orphan-page detector with AI-powered link suggestions; new dashboard tile (lazy-loaded so first visit is never blocked).
- /llms.txt generator - markdown index of your site for AI crawlers, with live preview in Tools.
- Search engine verification meta tags for Google, Bing, Yandex, Pinterest, Baidu, Norton, Alexa (paste-friendly: full tags are auto-stripped).
- Native GA4 snippet with admin-exclude and IP-anon defaults on.
- Smart capitalize titles, strip /category/ base, redirect attachments to parent (default ON), nofollow + new-tab external links, archive noindex (date/search ON by default).
- Redirect engine extended: 307 / 410 / 451 status codes, regex sources (~...~) with $1 back-reference support in the target.
- CSV redirect import (drop-zone or paste) with header auto-detection.
- Settings JSON export/import (drop-zone) - API keys deliberately excluded.
- Find & Replace tool across post titles, content, excerpts, image alt/title/caption, with dry-run + diff preview.
- Tools page reorganized into 6 tabs: Import / Redirects CSV / Find & Replace / Orphan pages / llms.txt preview / Settings IO.
- Settings: 3 new tabs (Schema, Tracking & Verification, Links & Archives) using a card-grid + toggle-switch picker.
- Per-post metabox: AI helper panel, FAQ items with add/remove, primary category picker (when 2+ cats), custom JSON-LD with live validation badge.
- All "Start" actions are now "Stop"-able mid-flight: Media bulk alt, Content bulk-generate, Content bulk-apply, Migrator imports - all with server-side cancel transients and instant UI feedback.
- Loading bars converted to layered indeterminate animation (sliding gradient + diagonal stripes) so motion is guaranteed even when backend percentages stall. Skeleton-line and bouncing-dot loaders added.
- Mobile UX overhaul: feature cards collapse to single column, tabs horizontal-scroll, forms get 16px font (no iOS zoom-on-focus), tap targets ≥40px.
- Light-mode-only UI (dark-mode CSS overrides removed; they were the cause of unreadable text on systems with OS dark mode).
- Quick Setup defaults now explicitly enumerate every v2.1 option so a fresh install has every safe feature on without needing user input.
- Prominent "AI runs on YOUR account" notice in setup wizard, AI Providers settings, and per-post metabox.
Bug fixes in 2.1.0
- Settings export now downloads a clean JSON file (was double-encoded via WP_REST_Response wrapper).
- Schema::should_emit() exposed to public so v2.1 schema modules can gate their output (was private - would fatal on every page).
- Redirect engine: explicit exit; after wp_die() on 410/451 to guarantee no later hooks fire.
- Native-fetch path of BPS.rest() now preserves WP_Error code and data, fixing the "Open settings" modal that reads err.code === 'no_provider'.
- Orphan-detector scan moved off the synchronous dashboard render path - large sites no longer block on first visit.
- Archive noindex feature actually emits the meta tag (was wired to a filter nothing applied).
2.0.0
- MAJOR REWRITE: full standalone SEO plugin.
- New 1-Click Optimize button on the dashboard with live progress, pause, cancel, retry, and score-delta feedback.
- New Quick Setup wizard with three profiles (Blog / WooCommerce / News).
- New SEO Health Score (0-100) on the dashboard, score-component breakdown, and SEO health WP dashboard widget.
- New: standalone XML sitemap, Article/Breadcrumb/Organization schema, Open Graph + Twitter cards, canonical, noindex, robots.txt editor, 301 redirects + 404 logger.
- New per-post meta box with SERP preview, character counters, regenerate buttons.
- New consolidated Content page (replaces separate Posts/Pages/Products pages).
- New Audit Log of every AI change.
- New AI provider fallback chain (Pollinations → OpenRouter → Gemini by default).
- New cost tracker + monthly budget cap.
- New vision support for image alt text (Gemini, OpenRouter, Pollinations, Ollama llava).
- SEO data adapter system supports BinaryPH (Native), Yoast, Rank Math, and AIOSEO.
- API keys now encrypted at rest with libsodium (auto-migrated on upgrade).
- Brand-new admin UI with design tokens, dark mode, accessibility (WCAG AA), responsive layouts, toast notifications, modals, empty states.
- Full i18n support - every string translatable. Domain Path: /languages.
- New uninstall.php that fully cleans up options, transients, custom tables, capabilities, and post meta.
- New "Import from another SEO plugin" tool - copies post meta, redirects, and 404 logs from Rank Math, Yoast SEO, or All in One SEO into BinaryPH AI SEO's native storage. Source plugin data is never modified, so it serves as a backup.
Bug fixes in 2.0.0
- CRITICAL: Gemini API was failing silently due to wrong auth method. Now uses ?key= query string per Google docs.
- Prompt injection hardening - post titles are now sanitized before going into the prompt.
- AI responses are cleaned up (strips quotes, asterisks, "Focus keyword:" prefixes, takes only first line).
- AI responses are cached for 1 hour to avoid re-billing on transient failures.
- Concurrency lock on the cron worker prevents overlapping runs from double-billing.
- Cron worker context is now cleanly split (no more $_POST inspection inside cron).
- Fixed unindexed JOIN in the missing-keywords query (now casts attachment ID and guards against malformed values).
- Activation no longer schedules cron when scheduling is disabled.
- All admin notices include error details parsed from provider JSON, not silent failures.
- Pollinations endpoint set to https://gen.pollinations.ai/v1/chat/completions per the latest API docs.
- Pollinations now requires an API key (free tier from enter.pollinations.ai) - wizard guides users through signup.
- Asset cache-busting now uses file mtime + plugin version constant.
- Multisite-aware uninstall (cleans every site on network deletes).
1.0.5
- Added meta description generation, image alt text injection, better detection of missing fields.
1.0.4
- WP 6.9.1 compatibility, Pollinations.AI provider, WP Cron improvements, nonce fix.
1.0.3
- WooCommerce, Yoast, and Rank Math optimization.