🔵

GEO for WordPress: Complete Implementation Guide

beginner

WordPress implements GEO via Yoast SEO (recommended) for automatic meta tags and schema generation, or via custom functions.php code. Add wp_head() hooks for article dates and Open Graph. WordPress already generates static HTML, making it natively compatible with AI crawlers.

GEO for WordPress: Complete Implementation Guide

WordPress implements GEO via Yoast SEO (recommended) for automatic meta tags and schema generation, or via custom functions.php code. Add wp_head() hooks for article dates and Open Graph. WordPress already generates static HTML, making it natively compatible with AI crawlers.

WordPress has a natural advantage for GEO: it renders server-side HTML by default, so AI crawlers can read your content without JavaScript. The main work is ensuring complete meta tags, schema markup, and proper robots.txt configuration.

Yoast SEO Premium and RankMath Pro generate all required GEO meta tags and schema automatically as of 2026. This is the fastest path to GEO compliance.

What Yoast SEO handles automatically:

  • Title and meta description
  • Open Graph tags (og:type, og:title, og:description, og:image, og:url)
  • article:published_time and article:modified_time
  • JSON-LD Article schema with publisher and dates
  • BreadcrumbList schema
  • Organization schema on homepage
  • FAQPage schema via Gutenberg FAQ block

Yoast SEO configuration:

  1. Install Yoast SEO (free) or Yoast SEO Premium
  2. Go to SEO → Search Appearance → configure site name and logo
  3. Go to SEO → Social → enable Open Graph
  4. For each post: fill in the SEO title and meta description in the Yoast panel
  5. For FAQPage schema: use the FAQ block in Gutenberg editor

Option 2: Custom functions.php

For developers who prefer full control, add this to your theme’s functions.php:

<?php
// functions.php

/**
 * Add complete GEO meta tags to singular posts and pages
 */
function geo_meta_tags() {
  if (!is_singular()) return;

  $published = get_the_date('c');
  $modified  = get_the_modified_date('c');
  $url       = get_permalink();
  $desc      = wp_strip_all_tags(get_the_excerpt() ?: get_bloginfo('description'));
  $image     = get_the_post_thumbnail_url(null, 'large');
  $author_id = get_post_field('post_author');
  $author_url = get_author_posts_url($author_id);

  echo '<meta name="author" content="' . esc_attr(get_the_author_meta('display_name', $author_id)) . '">' . "\n";
  echo '<link rel="canonical" href="' . esc_url($url) . '">' . "\n";

  // Open Graph
  echo '<meta property="og:type" content="article">' . "\n";
  echo '<meta property="og:title" content="' . esc_attr(get_the_title()) . '">' . "\n";
  echo '<meta property="og:description" content="' . esc_attr($desc) . '">' . "\n";
  echo '<meta property="og:url" content="' . esc_url($url) . '">' . "\n";
  echo '<meta property="og:site_name" content="' . esc_attr(get_bloginfo('name')) . '">' . "\n";
  echo '<meta property="og:locale" content="' . esc_attr(str_replace('-', '_', get_locale())) . '">' . "\n";
  if ($image) {
    echo '<meta property="og:image" content="' . esc_url($image) . '">' . "\n";
  }

  // Article dates (critical recency signal)
  echo '<meta property="article:published_time" content="' . esc_attr($published) . '">' . "\n";
  echo '<meta property="article:modified_time" content="' . esc_attr($modified) . '">' . "\n";
  echo '<meta property="article:author" content="' . esc_url($author_url) . '">' . "\n";

  // Article tags
  $tags = get_the_tags();
  if ($tags) {
    foreach ($tags as $tag) {
      echo '<meta property="article:tag" content="' . esc_attr($tag->name) . '">' . "\n";
    }
  }
}
add_action('wp_head', 'geo_meta_tags', 5);


/**
 * Add JSON-LD Article schema
 */
function geo_json_ld() {
  if (!is_singular()) return;

  $schema = [
    '@context'  => 'https://schema.org',
    '@type'     => 'Article',
    'headline'  => get_the_title(),
    'description' => wp_strip_all_tags(get_the_excerpt()),
    'author'    => [
      '@type' => 'Person',
      'name'  => get_the_author(),
      'url'   => get_author_posts_url(get_the_author_meta('ID')),
    ],
    'publisher' => [
      '@type' => 'Organization',
      'name'  => get_bloginfo('name'),
      'logo'  => [
        '@type' => 'ImageObject',
        'url'   => get_site_icon_url(512) ?: get_bloginfo('url') . '/logo.png',
      ],
    ],
    'datePublished'    => get_the_date('c'),
    'dateModified'     => get_the_modified_date('c'),
    'mainEntityOfPage' => [
      '@type' => 'WebPage',
      '@id'   => get_permalink(),
    ],
  ];

  $thumbnail = get_the_post_thumbnail_url(null, 'large');
  if ($thumbnail) {
    $schema['image'] = $thumbnail;
  }

  echo '<script type="application/ld+json">'
    . wp_json_encode($schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
    . '</script>' . "\n";
}
add_action('wp_head', 'geo_json_ld');


/**
 * Add Organization schema on homepage
 */
function geo_organization_schema() {
  if (!is_front_page()) return;

  $schema = [
    '@context'    => 'https://schema.org',
    '@type'       => 'Organization',
    'name'        => get_bloginfo('name'),
    'url'         => get_bloginfo('url'),
    'description' => get_bloginfo('description'),
    'logo'        => get_site_icon_url(512) ?: get_bloginfo('url') . '/logo.png',
  ];

  echo '<script type="application/ld+json">'
    . wp_json_encode($schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
    . '</script>' . "\n";
}
add_action('wp_head', 'geo_organization_schema');

robots.txt

WordPress generates robots.txt dynamically. To add AI crawlers, use the robots_txt filter:

// functions.php
function geo_robots_txt($output, $public) {
  if ($public) {
    $output .= "\nUser-agent: GPTBot\nAllow: /\n";
    $output .= "\nUser-agent: OAI-SearchBot\nAllow: /\n";
    $output .= "\nUser-agent: ClaudeBot\nAllow: /\n";
    $output .= "\nUser-agent: Claude-User\nAllow: /\n";
    $output .= "\nUser-agent: Claude-SearchBot\nAllow: /\n";
    $output .= "\nUser-agent: PerplexityBot\nAllow: /\n";
    $output .= "\nUser-agent: Google-Extended\nAllow: /\n";
    $output .= "\nSitemap: " . get_bloginfo('url') . "/llms.txt\n";
  }
  return $output;
}
add_filter('robots_txt', 'geo_robots_txt', 10, 2);

llms.txt via WordPress Page or Plugin

Create a custom endpoint for llms.txt:

// functions.php
function geo_llms_txt() {
  if ($_SERVER['REQUEST_URI'] === '/llms.txt') {
    header('Content-Type: text/plain');
    $site_name = get_bloginfo('name');
    $site_url = get_bloginfo('url');
    $description = get_bloginfo('description');

    echo "# {$site_name}\n";
    echo "> {$description}\n\n";
    echo "## Main Content\n";

    // Get recent posts
    $posts = get_posts(['numberposts' => 20, 'post_status' => 'publish']);
    foreach ($posts as $post) {
      $excerpt = wp_strip_all_tags(get_the_excerpt($post));
      echo "- [{$post->post_title}](" . get_permalink($post) . "): {$excerpt}\n";
    }

    echo "\n## About\n";
    echo "- [About Us]({$site_url}/about): Team and credentials\n";
    exit;
  }
}
add_action('init', 'geo_llms_txt');

XML Sitemap

Use the Yoast SEO sitemap (enabled by default) or install a dedicated sitemap plugin like “Simple Sitemap” or “Google XML Sitemaps”. All generate <lastmod> dates automatically from WordPress post modification times.

WordPress REST API provides modified timestamps — sitemap plugins use these for <lastmod>.

GEO Checklist for WordPress

  • Yoast SEO or RankMath installed and configured (recommended path)
  • OR: geo_meta_tags() and geo_json_ld() added to functions.php
  • robots_txt filter adds GPTBot, ClaudeBot, PerplexityBot, Google-Extended
  • llms.txt endpoint created or Yoast generates it
  • XML sitemap active with lastmod dates (Yoast or plugin)
  • Yoast SEO: Social settings → Open Graph enabled
  • Each post has SEO title and meta description filled in
  • Images have alt text (improves AI comprehension)
  • Inverted pyramid: answer in first paragraph of each post
  • Core Web Vitals: LCP < 2.5s, INP < 200ms, CLS < 0.1