Orravo SEO Kit v1.0.0
404 monitor, redirect manager, sitemap generator, robots.txt editor, and broken-link scanner: everything you need to protect Google traffic after a WordPress redesign or migration. Five tools, one plugin, no SaaS dashboard.
What Orravo SEO Kit does
After a redesign or domain migration, traffic dies on dropped URLs, broken inbound links, and missing redirects. Orravo SEO Kit is the focused toolkit that fixes those problems without installing a 60-MB all-in-one SEO suite.
template_redirect with a single indexed lookup.robots.txt from the admin. Per-user-agent allow / disallow, custom Sitemap: lines, environment overrides.Getting installed
Requirements
- WordPress 6.0 or newer
- PHP 8.0 or newer
- Pretty permalinks enabled (anything other than
?p=123) - Apache, Nginx, or LiteSpeed. SEO Kit does not require
.htaccessmodification.
From WordPress admin
orravo-seo-kit.zip, click Install Now, then Activate.osk_404_log, osk_redirects, osk_broken_links, osk_old_slugs. It also flushes rewrite rules once so the sitemap and robots endpoints resolve. The schema version is stored in osk_db_version.Up and running in minutes
Two-minute path to a healthy SEO baseline. Get the sitemap live, point Google at it, switch on 404 logging, done.
/sitemap.xml in a new tab to confirm it returns the sitemap index.https://your-domain.com/sitemap.xml to Google Search Console under Sitemaps.404 monitor
SEO Kit hooks into template_redirect with priority 99. When is_404() returns true, the request is logged, deduplicated by URL, and the hit counter is incremented.
What gets logged
| Column | Source |
|---|---|
url | $_SERVER['REQUEST_URI'], query-stripped, lower-cased, max 500 chars |
referrer | $_SERVER['HTTP_REFERER'] |
user_agent | $_SERVER['HTTP_USER_AGENT'] |
ip_hash | SHA-256 of the visitor IP plus the site's NONCE_SALT. Raw IP is never stored. |
hits | Incremented per duplicate URL hit |
first_seen / last_seen | Set on insert / update |
status | open, redirected (redirect created from this row), or ignored |
Filters
PHP// Skip logging some URLs entirely (e.g. asset noise from outdated cached pages)
add_filter( 'osk_should_log_404', function( bool $log, string $url ): bool {
if ( str_starts_with( $url, '/wp-content/uploads/' ) ) return false;
if ( str_ends_with( $url, '.map' ) ) return false;
return $log;
}, 10, 2 );
// Skip logging bot hits
add_filter( 'osk_should_log_404', function( bool $log ): bool {
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
return $log && ! preg_match( '/bot|crawler|spider|slurp/i', $ua );
} );
Auto-prune
By default, 404 rows older than 90 days with hits < 3 are pruned daily via the osk_prune_404_log cron event. Change the retention under 404 Monitor → Settings or via the osk_404_retention_days filter.
Redirect manager
Redirects are resolved in PHP. SEO Kit subscribes to template_redirect at priority 1, runs one indexed SELECT, and issues a wp_redirect() with the configured status code. No .htaccess edits, no Nginx reload.
Match types
| Type | Source format | Match |
|---|---|---|
exact | /old-page | String equality on request path (after lower-casing and trailing-slash strip). |
prefix | /blog/2019/* | str_starts_with against the request path. Captured suffix is available as $1 in the target. |
regex | ^/p/([0-9]+)$ | Full regex. Captures $1..$9 are substituted into the target. |
fuzzy | any string | Levenshtein distance <= 3 against the requested path. Used for typo recovery; default off. |
Status codes
- 301 Moved Permanently (default): cached aggressively by browsers and search engines. Use for permanent moves.
- 302 Found: temporary. Not cached. Useful during A/B tests or staged migrations.
- 307 Temporary Redirect: preserves the request method (POST stays POST). Useful for form-submission endpoints.
- 410 Gone: terminates the page with no redirect. Tells Google to drop the URL.
PHP API
PHP// Programmatic redirect insert
OSK_Redirects::save([
'source' => '/blog/2019/old-post',
'target' => '/blog/new-post',
'match_type' => 'exact', // exact | prefix | regex | fuzzy
'status_code' => 301,
'enabled' => true,
'note' => 'Manual migration, May 2026',
]);
// Wildcard with capture
OSK_Redirects::save([
'source' => '/blog/2019/*',
'target' => '/archive/$1',
'match_type' => 'prefix',
'status_code'=> 301,
]);
// Regex with multiple captures
OSK_Redirects::save([
'source' => '^/p/([0-9]+)/([a-z0-9\-]+)$',
'target' => '/posts/$2?legacy=$1',
'match_type' => 'regex',
'status_code'=> 301,
]);
Old-slug capture
When a published post's slug changes, WordPress already records the old slug in the _wp_old_slug postmeta. SEO Kit goes further: it writes a row to osk_old_slugs with the full old URL and the new URL, then auto-creates a 301 redirect with status auto. You can disable this under Redirects → Behaviour.
Import formats
| Source | Detection |
|---|---|
| SEO Kit CSV | Header row matches source,target,match_type,status_code,enabled,note |
| Redirection plugin | JSON export with url, action_data.url, regex fields |
| Rank Math Redirections | CSV export from Rank Math; sources, destination, type columns |
| Yoast Premium | CSV export from Yoast Premium redirect manager |
| Apache | RewriteRule lines from a .htaccess snippet |
| Nginx | rewrite and return 301 lines from an Nginx server {} block |
Sitemap generator
SEO Kit ships a fully cached XML sitemap index at /sitemap.xml. It complements (and can replace) WordPress core's auto-generated /wp-sitemap.xml.
Endpoints
| URL | Returns |
|---|---|
/sitemap.xml | Sitemap index linking to each sub-sitemap. |
/sitemap-post-1.xml | Page 1 of the post post type. Up to 50,000 URLs per page. |
/sitemap-page-1.xml | Page 1 of the page post type. |
/sitemap-product-1.xml | Per registered public CPT. |
/sitemap-tax-category-1.xml | Per public taxonomy. |
/sitemap-images.xml | Image sitemap with parent-post linkage. |
/sitemap.xml.gz | Gzip-compressed copy of the sitemap index. |
Exclusions
Out of the box, SEO Kit excludes: posts with noindex via core, password-protected posts, drafts and private posts, Woo cart / checkout / my-account, the auto-created OMembership portal page (if present), and any post tagged with the osk_exclude meta key.
PHP// Programmatic per-post exclusion
add_filter( 'osk_sitemap_should_include_post', function( bool $include, WP_Post $post ): bool {
if ( get_post_meta( $post->ID, '_legacy_archive', true ) ) {
return false;
}
return $include;
}, 10, 2 );
// Add a custom URL (e.g. a SPA route handled by your theme)
add_filter( 'osk_sitemap_extra_urls', function( array $urls ): array {
$urls[] = [
'loc' => home_url( '/app/dashboard' ),
'lastmod' => '2026-05-15',
'changefreq' => 'weekly',
'priority' => '0.7',
];
return $urls;
} );
Caching
Each sub-sitemap is rendered once per change, stored in a transient (osk_sitemap_post_1, etc.), and invalidated on save_post, delete_post, term changes, or a manual Rebuild now click. Cache TTL defaults to 12 hours.
Robots.txt editor
SEO Kit serves a virtual /robots.txt via WordPress's do_robots action. You edit the contents from the admin; no filesystem write is required.
Default output
TEXTUser-agent: *
Disallow: /wp-admin/
Allow: /wp-admin/admin-ajax.php
Sitemap: https://your-domain.com/sitemap.xml
Per-environment overrides
On a non-production environment (wp_get_environment_type() !== 'production'), SEO Kit automatically prepends a Disallow: / directive for all user agents. You can disable this safety net but it is highly recommended; it prevents staging URLs from being accidentally indexed.
Per-agent rules
Add agent-specific blocks from Robots → Rules. SEO Kit emits one User-agent: stanza per row, ordered by user-agent name. The wildcard * always comes last to avoid shadowing more specific rules.
Broken-link scanner
The scanner crawls your own posts (not the public web). It extracts every outbound link, deduplicates, then queues HTTP HEAD checks against each unique target.
Lifecycle
- 1Click Start scan under SEO Kit → Broken Links. SEO Kit walks posts via the WP REST API, page by page.
- 2Each post body is parsed for
<a href>tags; outbound links are stored with their source post ID. - 3Action Scheduler is loaded; one job per unique URL is queued (default concurrency: 4).
- 4Each job issues a HEAD request with a 7-second timeout. Non-2xx / non-3xx responses or connection errors are recorded.
- 5Once all jobs finish, the admin notifications panel shows the run summary; results live in
osk_broken_links.
Result columns
| Column | Description |
|---|---|
url | The target URL. |
status_code | Returned HTTP code, or 0 for DNS / connection failure. |
error | Error string from WP_Error::get_error_message() when applicable. |
occurrences | Number of posts containing this URL. |
posts | JSON-encoded list of post IDs and titles. |
last_checked | Timestamp of the most recent check. |
Fix-in-place
Each broken-link row has a Replace action: enter a new URL and SEO Kit walks the listed posts, runs a content-safe str_replace on the original URL, and saves the posts via wp_update_post(). Pre-edit revisions are preserved so a rollback is always possible.
REST API
All endpoints live under /wp-json/orravo-seo/v1/. Read endpoints require edit_posts; write endpoints require manage_options.
Endpoints
| Method | Path | Purpose |
|---|---|---|
| GET | /404s | Paginated list of 404 hits; filter by status and date range. |
| POST | /404s/{id}/redirect | Create a redirect from a 404 row; marks the row as redirected. |
| DELETE | /404s/{id} | Delete a 404 row. |
| GET | /redirects | Paginated list of redirects. |
| POST | /redirects | Create a redirect. |
| PUT | /redirects/{id} | Update a redirect. |
| DELETE | /redirects/{id} | Delete a redirect. |
| POST | /sitemap/rebuild | Invalidate sitemap transients and regenerate. |
| POST | /link-scan/start | Queue a new broken-link scan; returns the scan ID. |
| GET | /link-scan/{id} | Status of an in-flight scan. |
Create a redirect
cURLcurl -X POST https://example.com/wp-json/orravo-seo/v1/redirects \
-H 'Content-Type: application/json' \
-H 'X-WP-Nonce: <rest-nonce>' \
-d '{
"source": "/blog/2019/*",
"target": "/archive/$1",
"match_type": "prefix",
"status_code": 301,
"enabled": true,
"note": "2019 archive migration"
}'
Read 404 log
cURLcurl -X GET 'https://example.com/wp-json/orravo-seo/v1/404s?status=open&orderby=hits&per_page=20' \
-H 'X-WP-Nonce: <rest-nonce>'
Frequently asked questions
source column and on match_type. Exact-match redirects (the most common type) hit the index directly. Prefix and regex matches are evaluated in PHP only after the exact-match query misses, so an unfiltered exact-match lookup adds the same overhead as any single WordPress option read.NONCE_SALT is stored in the ip_hash column. The raw IP is never written to the database, so logs are GDPR-friendly by default. Disable IP hashing entirely under 404 Monitor → Settings if you want even less data captured.User-Agent: OrravoSEOKit/1.0 (link-scanner) header. Rate limit and concurrency are configurable under Broken Links → Settings. The scan is paused automatically if a target host returns three consecutive 429 responses.uninstall.php, which drops all four custom tables (404 log, redirects, broken links, old slugs).osk_sitemap_extra_links filter (returning alternate language URLs per entry). News and video sitemaps are on the roadmap..htaccess rules before WordPress boots; SEO Kit runs inside WordPress on template_redirect. If a URL is already rewritten at the Apache layer, WordPress never sees it. Keep server-level redirects for high-traffic permanent moves; use SEO Kit for everything else.uninstall.php, which drops the four custom tables and removes plugin options. Deactivating alone leaves the data in place. The virtual sitemap and robots.txt endpoints disappear immediately on deactivation; flush rewrite rules from Settings → Permalinks if any 404s persist.What's changed
- New404 monitor with hit deduplication, daily digest email, GDPR-safe IP hashing
- NewRedirect manager with exact, prefix, regex, and fuzzy matching
- NewStatus codes 301, 302, 307, 410
- NewOne-click redirect creation from any 404 row
- NewOld-slug capture and auto-301 on post slug change
- NewXML sitemap index at
/sitemap.xmlwith per-CPT and per-taxonomy sub-sitemaps - NewImage sitemap with parent-post linkage
- NewGzip-compressed sitemap output
- NewRobots.txt editor with per-agent rules and staging-environment auto-disallow
- NewBroken-link scanner powered by Action Scheduler
- NewFix-in-place link replacement across affected posts
- NewImporters for Redirection, Rank Math, Yoast Premium, Apache, Nginx
- NewCSV export for redirects
- NewREST API at
/wp-json/orravo-seo/v1/ - NewAuto-prune of stale 404 rows older than 90 days
- NewFour custom DB tables with
uninstall.phpcleanup
Got a question about SEO Kit?
Reach out directly. Kenneth replies within 24 hours.

