From claude-wp-builder
Rank Math SEO configuration reference, schema JSON-LD templates, meta patterns, and SEO seeding commands
How this skill is triggered — by the user, by Claude, or both
Slash command
/claude-wp-builder:wp-audit-seo-standardsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill defines the SEO configuration standards, schema templates, and seeding commands for WordPress sites using **Rank Math SEO** as the primary SEO plugin.
This skill defines the SEO configuration standards, schema templates, and seeding commands for WordPress sites using Rank Math SEO as the primary SEO plugin.
// Plugin slug: seo-by-rank-math
// Detection: defined('RANK_MATH_VERSION') or class_exists('RankMath')
// WP-CLI install: $WP plugin install seo-by-rank-math --activate
Before running any Rank Math commands, verify the plugin is active:
$WP eval "
if (defined('RANK_MATH_VERSION')) {
echo 'Rank Math v' . RANK_MATH_VERSION . ' is active.';
} else {
echo 'Rank Math is NOT active.';
}
"
| Module | Slug | Recommended | Notes |
|---|---|---|---|
| SEO Analysis | seo-analysis | Yes | On-page content scoring |
| Sitemap | sitemap | Yes | XML sitemap generation |
| Rich Snippets | rich-snippet | Yes | JSON-LD structured data |
| Breadcrumbs | breadcrumbs | Yes | Breadcrumb trail + schema |
| 404 Monitor | 404-monitor | Yes | Track broken links |
| Redirections | redirections | Yes | 301/302 redirect manager |
| Local SEO | local-seo | Yes (business sites) | LocalBusiness schema |
| Image SEO | image-seo | Yes | Auto alt/title attributes |
| Instant Indexing | instant-indexing | Yes | IndexNow / Bing |
| Link Counter | link-counter | Yes | Internal/external link audit |
$WP eval "
\$modules = get_option('rank_math_modules', []);
\$enable = ['seo-analysis','sitemap','rich-snippet','breadcrumbs','404-monitor','redirections','local-seo','image-seo','instant-indexing','link-counter'];
foreach (\$enable as \$mod) { if (!in_array(\$mod, \$modules)) { \$modules[] = \$mod; } }
update_option('rank_math_modules', \$modules);
echo 'Enabled modules: ' . implode(', ', \$modules);
"
| Option | Purpose |
|---|---|
rank_math_modules | Array of active module slugs |
rank-math-options-general | Breadcrumbs, links, image SEO, strip category base |
rank-math-options-titles | Title templates, schema type, social profiles, noindex rules |
rank-math-options-sitemap | Sitemap inclusions, ping settings |
rank-math-options-instant-indexing | IndexNow configuration |
# Get all general options
$WP eval "print_r(get_option('rank-math-options-general'));"
# Get all title options
$WP eval "print_r(get_option('rank-math-options-titles'));"
# Get active modules
$WP eval "print_r(get_option('rank_math_modules'));"
Rank Math stores options as serialized arrays. Always merge rather than overwrite:
$WP eval "
\$opts = (array) get_option('rank-math-options-general', []);
\$opts['breadcrumbs'] = 'on';
\$opts['strip_category_base'] = 'on';
\$opts['nofollow_external_links'] = 'on';
\$opts['new_window_external_links'] = 'on';
\$opts['add_img_alt'] = 'on';
\$opts['add_img_title'] = 'on';
update_option('rank-math-options-general', \$opts);
echo 'General options updated.';
"
| Meta Key | Purpose | Example Value |
|---|---|---|
rank_math_title | SEO title template | %title% %sep% %sitename% |
rank_math_description | Meta description | Free text, 150-160 chars |
rank_math_focus_keyword | Focus keyword(s) | keyword1,keyword2 |
rank_math_robots | Robots directives | ["index","follow"] |
rank_math_canonical_url | Canonical URL override | https://... |
rank_math_facebook_title | OG title | Free text |
rank_math_facebook_description | OG description | Free text |
rank_math_facebook_image | OG image URL | https://... |
rank_math_facebook_image_id | OG image attachment ID | 42 |
rank_math_twitter_card_type | Twitter card type | summary_large_image |
rank_math_twitter_title | Twitter title | Free text |
rank_math_twitter_description | Twitter description | Free text |
rank_math_schema_Article | Article schema | Serialized array |
rank_math_schema_FAQPage | FAQ schema | Serialized array |
rank_math_breadcrumb_title | Breadcrumb label override | Free text |
rank_math_pillar_content | Pillar content flag | on |
# Set SEO meta for a single post
$WP eval "
\$post_id = 42;
update_post_meta(\$post_id, 'rank_math_title', '%title% %sep% %sitename%');
update_post_meta(\$post_id, 'rank_math_description', 'Your meta description here.');
update_post_meta(\$post_id, 'rank_math_focus_keyword', 'primary keyword');
update_post_meta(\$post_id, 'rank_math_robots', ['index','follow']);
update_post_meta(\$post_id, 'rank_math_twitter_card_type', 'summary_large_image');
echo 'SEO meta set for post ' . \$post_id;
"
$WP eval "
\$posts = get_posts(['post_type' => ['post','page'], 'posts_per_page' => -1, 'post_status' => 'publish']);
foreach (\$posts as \$p) {
\$existing = get_post_meta(\$p->ID, 'rank_math_description', true);
if (!empty(\$existing)) continue;
\$text = wp_strip_all_tags(\$p->post_excerpt ?: \$p->post_content);
\$desc = mb_substr(trim(preg_replace('/\s+/', ' ', \$text)), 0, 155);
if (mb_strlen(\$desc) > 10) {
update_post_meta(\$p->ID, 'rank_math_description', \$desc);
echo 'Set description for: ' . \$p->post_title . PHP_EOL;
}
}
"
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "",
"url": "",
"logo": {
"@type": "ImageObject",
"url": ""
},
"sameAs": [
"https://www.facebook.com/PROFILE",
"https://www.instagram.com/PROFILE",
"https://x.com/PROFILE",
"https://www.linkedin.com/company/PROFILE"
]
}
{
"@context": "https://schema.org",
"@type": "LocalBusiness",
"name": "",
"url": "",
"image": "",
"telephone": "",
"email": "",
"address": {
"@type": "PostalAddress",
"streetAddress": "",
"addressLocality": "",
"addressRegion": "",
"postalCode": "",
"addressCountry": ""
},
"geo": {
"@type": "GeoCoordinates",
"latitude": "",
"longitude": ""
},
"openingHoursSpecification": [
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
"opens": "09:00",
"closes": "17:00"
}
],
"sameAs": []
}
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "What is the question?",
"acceptedAnswer": {
"@type": "Answer",
"text": "The answer to the question."
}
},
{
"@type": "Question",
"name": "Another question?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Another answer."
}
}
]
}
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "https://example.com/"
},
{
"@type": "ListItem",
"position": 2,
"name": "Category",
"item": "https://example.com/category/"
},
{
"@type": "ListItem",
"position": 3,
"name": "Current Page"
}
]
}
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "",
"author": {
"@type": "Person",
"name": "",
"url": ""
},
"datePublished": "2025-01-01T00:00:00+00:00",
"dateModified": "2025-01-01T00:00:00+00:00",
"image": {
"@type": "ImageObject",
"url": "",
"width": 1200,
"height": 630
},
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://example.com/post-slug/"
},
"publisher": {
"@type": "Organization",
"name": "",
"logo": {
"@type": "ImageObject",
"url": ""
}
}
}
| Page Type | Formula |
|---|---|
| Homepage | %sitename% %sep% %sitedesc% |
| Page | %title% %sep% %sitename% |
| Blog Post | %title% %sep% %sitename% |
| Category | %term% %sep% %sitename% %page% |
| Search | Search: %searchphrase% %sep% %sitename% |
| 404 | Page Not Found %sep% %sitename% |
| Author | %name% %sep% %sitename% |
%title% — Post/page title%sitename% — Site name from Settings > General%sitedesc% — Site tagline%sep% — Separator character (default: -)%term% — Category/tag name%searchphrase% — Current search query%page% — Page number (blank on page 1)%name% — Author display name%date% — Post publication date%excerpt% — Post excerpt$WP eval "
\$opts = (array) get_option('rank-math-options-titles', []);
\$opts['homepage_title'] = '%sitename% %sep% %sitedesc%';
\$opts['pt_post_title'] = '%title% %sep% %sitename%';
\$opts['pt_page_title'] = '%title% %sep% %sitename%';
\$opts['tax_category_title'] = '%term% %sep% %sitename% %page%';
\$opts['search_title'] = 'Search: %searchphrase% %sep% %sitename%';
\$opts['404_title'] = 'Page Not Found %sep% %sitename%';
\$opts['author_archive_title'] = '%name% %sep% %sitename%';
update_option('rank-math-options-titles', \$opts);
echo 'Title templates updated.';
"
rank_math_description post meta (highest priority)<p> block in post contentTarget length: 150–160 characters.
function prefix_auto_meta_description( $post_id ) {
$existing = get_post_meta( $post_id, 'rank_math_description', true );
if ( ! empty( $existing ) ) {
return $existing;
}
$post = get_post( $post_id );
if ( ! $post ) {
return '';
}
// Try excerpt first
if ( ! empty( $post->post_excerpt ) ) {
$text = $post->post_excerpt;
} else {
// Try first paragraph
$content = $post->post_content;
if ( preg_match( '/<p[^>]*>(.*?)<\/p>/is', $content, $matches ) ) {
$text = $matches[1];
} else {
$text = $content;
}
}
$text = wp_strip_all_tags( $text );
$text = trim( preg_replace( '/\s+/', ' ', $text ) );
$desc = mb_substr( $text, 0, 155 );
// Avoid cutting mid-word
if ( mb_strlen( $text ) > 155 ) {
$desc = mb_substr( $desc, 0, mb_strrpos( $desc, ' ' ) );
$desc .= '...';
}
return $desc;
}
$WP eval "
\$posts = get_posts(['post_type' => ['post','page'], 'posts_per_page' => -1, 'post_status' => 'publish']);
\$count = 0;
foreach (\$posts as \$p) {
\$existing = get_post_meta(\$p->ID, 'rank_math_description', true);
if (!empty(\$existing)) continue;
\$text = wp_strip_all_tags(\$p->post_excerpt ?: \$p->post_content);
\$text = trim(preg_replace('/\s+/', ' ', \$text));
\$desc = mb_substr(\$text, 0, 155);
if (mb_strlen(\$text) > 155) {
\$desc = mb_substr(\$desc, 0, mb_strrpos(\$desc, ' '));
}
if (mb_strlen(\$desc) > 10) {
update_post_meta(\$p->ID, 'rank_math_description', \$desc);
\$count++;
}
}
echo \"Seeded \$count meta descriptions.\";
"
# {Site Name}
> {Site Description}
## Pages
- [Page Title](url): excerpt or first 20 words
## Blog Posts
- [Post Title](url): excerpt
## Contact
- Website: {home_url}
$WP eval "
\$name = get_bloginfo('name');
\$desc = get_bloginfo('description');
\$home = home_url('/');
\$out = \"# \$name\n\n> \$desc\n\n\";
// Pages
\$out .= \"## Pages\n\";
\$pages = get_posts(['post_type' => 'page', 'posts_per_page' => -1, 'post_status' => 'publish', 'orderby' => 'menu_order', 'order' => 'ASC']);
foreach (\$pages as \$p) {
\$url = get_permalink(\$p->ID);
\$excerpt = \$p->post_excerpt ?: wp_trim_words(wp_strip_all_tags(\$p->post_content), 20, '...');
\$out .= \"- [\$p->post_title](\$url): \$excerpt\n\";
}
// Blog posts
\$out .= \"\n## Blog Posts\n\";
\$posts = get_posts(['post_type' => 'post', 'posts_per_page' => -1, 'post_status' => 'publish', 'orderby' => 'date', 'order' => 'DESC']);
foreach (\$posts as \$p) {
\$url = get_permalink(\$p->ID);
\$excerpt = \$p->post_excerpt ?: wp_trim_words(wp_strip_all_tags(\$p->post_content), 20, '...');
\$out .= \"- [\$p->post_title](\$url): \$excerpt\n\";
}
// Contact
\$out .= \"\n## Contact\n\";
\$out .= \"- Website: \$home\n\";
file_put_contents(ABSPATH . 'llms.txt', \$out);
echo 'Generated llms.txt at ' . ABSPATH . 'llms.txt';
echo PHP_EOL . '---' . PHP_EOL . \$out;
"
# robots.txt for WordPress
User-agent: *
Allow: /
Disallow: /wp-admin/
Allow: /wp-admin/admin-ajax.php
Disallow: /wp-includes/
Disallow: /wp-content/plugins/
Disallow: /readme.html
Disallow: /license.txt
Disallow: /?s=
Disallow: /search/
Disallow: /cgi-bin/
Disallow: /trackback/
# AI Crawlers — Allow by default (confirm with user before changing)
User-agent: GPTBot
Allow: /
User-agent: ClaudeBot
Allow: /
User-agent: Google-Extended
Allow: /
User-agent: PerplexityBot
Allow: /
User-agent: CCBot
Allow: /
User-agent: Bytespider
Disallow: /
# Sitemap
Sitemap: {home_url}/sitemap_index.xml
Note: AI crawler policy (Allow vs Disallow) should be confirmed with the site owner before writing. The defaults above allow all major AI crawlers except Bytespider.
$WP eval "
\$home = home_url('/');
\$robots = 'User-agent: *
Allow: /
Disallow: /wp-admin/
Allow: /wp-admin/admin-ajax.php
Disallow: /wp-includes/
Disallow: /wp-content/plugins/
Disallow: /readme.html
Disallow: /license.txt
Disallow: /?s=
Disallow: /search/
Disallow: /cgi-bin/
Disallow: /trackback/
# AI Crawlers
User-agent: GPTBot
Allow: /
User-agent: ClaudeBot
Allow: /
User-agent: Google-Extended
Allow: /
User-agent: PerplexityBot
Allow: /
User-agent: CCBot
Allow: /
User-agent: Bytespider
Disallow: /
Sitemap: ' . \$home . 'sitemap_index.xml
';
file_put_contents(ABSPATH . 'robots.txt', \$robots);
echo 'Generated robots.txt at ' . ABSPATH . 'robots.txt';
"
function prefix_breadcrumbs() {
if (is_front_page()) return;
echo '<nav class="breadcrumbs" aria-label="Breadcrumb">';
if (function_exists('rank_math_the_breadcrumbs')) {
rank_math_the_breadcrumbs();
} else {
echo '<a href="' . esc_url(home_url('/')) . '">Home</a>';
echo ' » ';
if (is_singular()) { the_title(); }
elseif (is_archive()) { the_archive_title(); }
elseif (is_search()) { echo 'Search results'; }
elseif (is_404()) { echo 'Page Not Found'; }
}
echo '</nav>';
}
.breadcrumbs {
padding: 12px 0;
font-size: 0.875rem;
color: #6b7280;
}
.breadcrumbs a {
color: #3b82f6;
text-decoration: none;
}
.breadcrumbs a:hover {
text-decoration: underline;
}
.breadcrumbs .separator {
margin: 0 0.5rem;
color: #9ca3af;
}
$WP eval "
\$opts = (array) get_option('rank-math-options-general', []);
\$opts['breadcrumbs'] = 'on';
update_option('rank-math-options-general', \$opts);
echo 'Breadcrumbs enabled.';
"
FAQ content can appear in several forms. Detect and convert to JSON-LD schema automatically.
faq_items, faqs, faq_cards, faq<details>/<summary> HTML — accordion-style FAQ in content? — followed by <p> answer contentfunction prefix_detect_faq_jsonld( $post_id ) {
$faqs = [];
// 1. Check ACF repeater fields
$acf_names = ['faq_items', 'faqs', 'faq_cards', 'faq'];
foreach ( $acf_names as $field_name ) {
if ( function_exists('have_rows') && have_rows( $field_name, $post_id ) ) {
while ( have_rows( $field_name, $post_id ) ) {
the_row();
$q = get_sub_field('question') ?: get_sub_field('title');
$a = get_sub_field('answer') ?: get_sub_field('content') ?: get_sub_field('text');
if ( $q && $a ) {
$faqs[] = [ 'q' => wp_strip_all_tags($q), 'a' => wp_strip_all_tags($a) ];
}
}
}
}
// 2. Check <details>/<summary> in content
$content = get_post_field('post_content', $post_id);
if ( preg_match_all('/<summary[^>]*>(.*?)<\/summary>\s*(.*?)<\/details>/is', $content, $matches, PREG_SET_ORDER) ) {
foreach ( $matches as $m ) {
$q = wp_strip_all_tags( $m[1] );
$a = wp_strip_all_tags( $m[2] );
if ( $q && $a ) {
$faqs[] = [ 'q' => $q, 'a' => $a ];
}
}
}
// 3. Check H2/H3 headings ending with ?
if ( preg_match_all('/<h[23][^>]*>(.*?\?)<\/h[23]>\s*<p>(.*?)<\/p>/is', $content, $matches, PREG_SET_ORDER) ) {
foreach ( $matches as $m ) {
$q = wp_strip_all_tags( $m[1] );
$a = wp_strip_all_tags( $m[2] );
if ( $q && $a ) {
$faqs[] = [ 'q' => $q, 'a' => $a ];
}
}
}
if ( empty( $faqs ) ) {
return '';
}
// Build JSON-LD
$schema = [
'@context' => 'https://schema.org',
'@type' => 'FAQPage',
'mainEntity' => [],
];
foreach ( $faqs as $faq ) {
$schema['mainEntity'][] = [
'@type' => 'Question',
'name' => $faq['q'],
'acceptedAnswer' => [
'@type' => 'Answer',
'text' => $faq['a'],
],
];
}
return '<script type="application/ld+json">' . wp_json_encode( $schema, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT ) . '</script>';
}
add_action('wp_head', function() {
if ( is_singular() ) {
$jsonld = prefix_detect_faq_jsonld( get_the_ID() );
if ( $jsonld ) {
echo $jsonld;
}
}
});
Recommended order for bulk SEO setup on a new or existing site:
$WP plugin install seo-by-rank-math --activaterank_math_title, rank_math_focus_keyword, robots (Section 4)$WP eval "
if (class_exists('RankMath')) {
// Trigger sitemap regeneration
delete_transient('rank_math_sitemap_cache');
\RankMath\Sitemap\Cache::invalidate_storage();
}
"
$WP rewrite flush
After seeding, verify:
# Check title templates are set
$WP eval "print_r(get_option('rank-math-options-titles'));"
# Count posts with meta descriptions
$WP eval "
global \$wpdb;
\$count = \$wpdb->get_var(\"SELECT COUNT(*) FROM \$wpdb->postmeta WHERE meta_key = 'rank_math_description' AND meta_value != ''\");
echo \"Posts with meta descriptions: \$count\";
"
# Verify sitemap exists
$WP eval "echo home_url('/sitemap_index.xml');"
# Verify robots.txt exists
$WP eval "echo file_exists(ABSPATH . 'robots.txt') ? 'robots.txt exists' : 'robots.txt missing';"
# Verify llms.txt exists
$WP eval "echo file_exists(ABSPATH . 'llms.txt') ? 'llms.txt exists' : 'llms.txt missing';"
npx claudepluginhub yojahny55/claude-wp-builder --plugin claude-wp-builderCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.