WP Manifestindependent plugin directory
manifest / seo / headless-seo-kit

Headless SEO Kit

WordPress companion plugin for BeyondSEO / RankingCoach. Renders SEO tags, exposes meta via REST for AI/MCP workflows, syncs to BeyondSEO's internal table, handles postmeta-driven 301 slug redirects.

by Columbia Cloudworks LLC · github.com/columbia-cloudworks-llc/headless-seo-kit · website

0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/columbia-cloudworks-llc/headless-seo-kit/archive/refs/heads/main.zip

A WordPress companion plugin for BeyondSEO / RankingCoach that does the four things BeyondSEO doesn't:

  1. Renders SEO tags. BeyondSEO stores SEO data in postmeta but doesn't reliably emit <meta> tags on the front end (especially on IONOS Managed WordPress). This plugin reads the same rankingcoach_* postmeta keys and emits proper title overrides, meta description, canonical, Open Graph, Twitter Card, and JSON-LD Article schema.
  2. Exposes the keys via REST. The rankingcoach_* keys are registered with show_in_rest=true, so headless CMS workflows, the WP REST API, and AI agents using MCP (e.g. Easy MCP AI) can read and write them programmatically.
  3. Syncs back to BeyondSEO's internal table. BeyondSEO's React admin UI reads from its own Doctrine-backed table ({prefix}rankingcoach_metatags), not postmeta. When anything other than the BeyondSEO UI writes postmeta, the admin UI goes stale. This plugin mirrors writes into that internal table so the UI reflects reality.
  4. Handles postmeta-driven 301 redirects. Each post can declare its prior slugs via a single postmeta key. Old URLs 301 to the current permalink. No options page, no database table, no manual config — write the meta key and it just works.

Built and field-tested on IONOS Managed WordPress with BeyondSEO v1.2.0, WordPress 6.9, PHP 8.1.

Why this exists

BeyondSEO (sold by IONOS as part of "RankingCoach SEO" on their Managed WordPress plans) ships with three structural gaps:

  • It stores SEO metadata in rankingcoach_* postmeta keys but its front-end emitter is unreliable. Posts edited via the BeyondSEO UI sometimes render the tags; posts edited any other way usually don't.
  • Its postmeta keys are not registered with WordPress's REST API. Any tool that authors content through wp_update_post_meta() or the REST API can write the meta but can't see it back.
  • Its internal admin UI reads from a parallel Doctrine table, not from postmeta. Direct postmeta writes desync the UI.

If you're running an AI-assisted publishing pipeline against a WordPress site that has BeyondSEO bundled with the hosting plan, you can't uninstall BeyondSEO (it's on a managed host) and you can't make it work properly out of the box. This plugin closes the four gaps without fighting BeyondSEO — it reads the same data, fills in the missing rendering, and mirrors writes back so BeyondSEO's UI keeps showing the current state.

What this plugin doesn't do

  • It doesn't replace BeyondSEO. If BeyondSEO emits a tag on its own, this plugin doesn't suppress it (unless you enable strict mode). The two coexist.
  • It doesn't score content. No keyword analysis, no readability checker, no recommendations panel. That's BeyondSEO's job. This plugin just makes sure the data BeyondSEO already has gets rendered and synced.
  • It doesn't manage redirects through a UI. Redirects are postmeta-driven. Set the hsk_old_slugs meta key on a post via the WP admin meta editor, the REST API, WP-CLI, or any MCP client. There's no separate redirect manager screen.
  • It doesn't optimize images. Image SEO is out of scope.

Installation

  1. Download the latest release ZIP from the releases page (or build one yourself: zip -r headless-seo-kit.zip headless-seo-kit/).
  2. WordPress admin → Plugins → Add New → Upload Plugin → select the ZIP → Install Now → Activate.
  3. Optional: Settings → Headless SEO Kit. Set your Twitter handle, default OG image, and publisher name.
  4. If you're migrating from cab-agent-kit, your existing cab_agent_old_slugs postmeta values keep working — no migration required.

Requirements: WordPress 5.6+, PHP 7.4+. No external dependencies. Works alongside BeyondSEO but doesn't require it (the BeyondSEO internal-table sync becomes a silent no-op if the table isn't present).

Postmeta schema

All keys live on the post post type and are registered with show_in_rest=true.

Meta key Type Purpose
rankingcoach_seo_title string Overrides <title> and OG title. Recommended ≤60 chars.
rankingcoach_seo_description string Meta description + OG description fallback. Recommended 150–160 chars.
rankingcoach_seo_keywords string Comma-separated keywords. If empty, falls back to primary + secondary.
rankingcoach_primary_keyword string Primary keyword. Used if seo_keywords is empty.
rankingcoach_secondary_keywords string Comma-separated. Used if seo_keywords is empty.
rankingcoach_social_title string OG / Twitter title. Falls back to seo_title.
rankingcoach_social_description string OG / Twitter description. Falls back to seo_description.
rankingcoach_schema_type string Schema.org @type. Defaults to BlogPosting.
rankingcoach_schema_cache string (Read-only here.) If present and valid JSON, emitted verbatim as JSON-LD. Set by BeyondSEO.
hsk_old_slugs string Comma-separated prior slugs (no leading/trailing /). 301 to current permalink.
cab_agent_old_slugs string Legacy alias. Read for back-compat with prior cab-agent-kit deploys. Write to hsk_old_slugs on new posts.

Example: setting via REST

curl -X POST https://example.com/wp-json/wp/v2/posts/123 \
  -u user:application_password \
  -H 'Content-Type: application/json' \
  -d '{
    "meta": {
      "rankingcoach_seo_title": "Headless SEO Kit: Make BeyondSEO Actually Render",
      "rankingcoach_seo_description": "A WordPress plugin that fills the four gaps BeyondSEO leaves on IONOS Managed WordPress: rendering, REST visibility, internal-table sync, and slug redirects.",
      "rankingcoach_primary_keyword": "headless seo",
      "hsk_old_slugs": "old-slug-1,old-slug-2"
    }
  }'

Example: setting via WP-CLI

wp post meta update 123 rankingcoach_seo_title "New Title"
wp post meta update 123 hsk_old_slugs "old-url,older-url"

How rendering works

On every singular post or page request:

  1. WordPress core's rel_canonical is suppressed (prevents duplicate canonical tags).
  2. Headless SEO Kit emits its own block at wp_head priority 0, bracketed by / comments.
  3. Tags emitted: <meta name="description">, <meta name="keywords">, <link rel="canonical">, full Open Graph article set, Twitter Card summary_large_image, and JSON-LD schema.
  4. pre_get_document_title and wp_title are filtered so the <title> element reflects rankingcoach_seo_title if set.

Strict mode (optional, off by default)

If a third-party plugin starts emitting conflicting rankingcoach_* or beyondseo_* tags into <head> and you can't disable the offender, enable strict mode (Settings → Headless SEO Kit → Strict mode). An output buffer wraps wp_head and strips matching lines from everything outside this plugin's own block. Leave it off unless you have a confirmed conflict.

How the BeyondSEO sync works

BeyondSEO has two storage layers:

  • Postmeta (rankingcoach_* keys) — what the front-end emitter reads.
  • Doctrine table ({prefix}rankingcoach_metatags) — what the React admin UI reads.

The UI writes both. The front-end reads postmeta. Anyone else (REST API, WP-CLI, MCP, headless CMS) writing postmeta directly desyncs the UI.

This plugin hooks added_post_meta and updated_post_meta. When one of the five user-facing keys (rankingcoach_seo_title, _seo_description, _seo_keywords, _social_title, _social_description) is written, the corresponding (post_id, type) row is upserted into the internal table. The internal-table unique_key algorithm is reverse-engineered from BeyondSEO's WPWebPageMetaTag::uniqueKey() source:

strtolower($post_id . '_' . $type . '_' . md5($post_id . $type))

Sync is idempotent — same content = no-op. If the internal table doesn't exist (BeyondSEO not installed), the sync silently no-ops.

How redirects work

Set the hsk_old_slugs postmeta on a post to a comma-separated list of prior slugs:

old-url-1,older-url,really-old-name

On every front-end request, the plugin:

  1. Builds (and caches for 12 hours) a slug → post_id map from postmeta.
  2. Trims the request path to its slug-relative form.
  3. If the slug matches an entry, 301-redirects to the post's current permalink.

The cache is busted automatically on save_post, trashed_post, untrashed_post, deleted_post, and any hsk_old_slugs / cab_agent_old_slugs meta write. Self-redirects are blocked. Admin, REST, AJAX, cron, and preview requests are skipped.

Hooks reference

The plugin doesn't currently expose its own filters or actions — it just hooks WordPress core. If you want to extend it (e.g. add og:image:width, suppress JSON-LD on certain post types), the cleanest approach right now is to fork or to hook wp_head at priority 0 yourself and call remove_action( 'wp_head', 'hsk_render_head', 0 ); from your own plugin.

Filters / actions are on the roadmap. PRs welcome.

Compatibility notes

  • IONOS Managed WordPress: tested as the primary target. Works on shared and dedicated plans.
  • BeyondSEO: tested with v1.2.0. The internal-table schema is reverse-engineered, not documented by BeyondSEO. If they change the table structure, this plugin's sync becomes a no-op (it checks for table existence) but rendering still works.
  • WordPress core canonical: suppressed only on singular publish posts. Archives, search, etc. are unaffected.
  • Other SEO plugins: this plugin assumes it's the sole emitter of <meta name="description">, OG, Twitter Card, and JSON-LD for singular posts. If you're running Yoast / Rank Math / All in One SEO, deactivate them or accept duplicate tags.

License

GPL-2.0-or-later. See LICENSE.

Author

Columbia Cloudworks LLC — Nicholas E. King

Built originally to power the AI-assisted publishing pipeline behind The CAB Call.

Contributing

Issues and PRs welcome at github.com/Columbia-Cloudworks-LLC/headless-seo-kit.