WordPress Integration

Replace default WordPress sitemaps with SitemapHost using WP-CLI, webhooks, or a custom plugin

This guide covers how to integrate SitemapHost with a WordPress site. You will learn how to collect URLs from WordPress, push them to SitemapHost, disable the built-in or Yoast sitemap, and automate updates via WP-CLI or webhooks.

Prerequisites: A SitemapHost account, a configured domain, and an API key. See the Quick Start guide if you haven't set these up yet.

Why Use SitemapHost with WordPress?

WordPress generates sitemaps via its built-in wp-sitemap.xml (since WP 5.5) or through plugins like Yoast SEO. However, these have limitations:

  • Performance -- Sitemap generation on each request can slow down large sites
  • No CDN delivery -- Served from your origin server, adding load
  • Limited splitting -- WordPress splits by content type but not by optimized chunk sizes
  • No search engine notification -- You need additional plugins for IndexNow or GSC auto-submit
  • Headless WordPress -- If your frontend is decoupled (Next.js, Gatsby, etc.), WP sitemaps point to the wrong URLs

SitemapHost solves all of these. Push your URLs once, and we handle hosting, splitting, CDN delivery, and search engine notifications.

Option 1: WP-CLI Script

The most straightforward approach for WordPress. Use WP-CLI to extract all published posts and pages, then push them to SitemapHost.

Create the script

Save this as push-sitemap.sh in your WordPress root:

#!/bin/bash
# push-sitemap.sh -- Push WordPress URLs to SitemapHost

SITEMAPHOST_API_KEY="${SITEMAPHOST_API_KEY:-sk_live_xxxxxxxxxxxxxxxx}"
SITEMAPHOST_DOMAIN="${SITEMAPHOST_DOMAIN:-sitemap.yoursite.com}"
SITE_URL="${SITE_URL:-https://yoursite.com}"

# Fetch all published post URLs using WP-CLI
echo "Collecting URLs from WordPress..."

# Get pages
PAGES=$(wp post list --post_type=page --post_status=publish --fields=ID,post_name,post_modified --format=json)

# Get posts
POSTS=$(wp post list --post_type=post --post_status=publish --fields=ID,post_name,post_modified --format=json)

# Build JSON payload using a helper script
php -r "
\$pages = json_decode('$PAGES', true);
\$posts = json_decode('$POSTS', true);
\$urls = [];

foreach (\$pages as \$page) {
    \$permalink = trim(shell_exec('wp post url ' . \$page['ID']));
    \$urls[] = [
        'loc' => \$permalink,
        'lastmod' => date('Y-m-d', strtotime(\$page['post_modified'])),
    ];
}

foreach (\$posts as \$post) {
    \$permalink = trim(shell_exec('wp post url ' . \$post['ID']));
    \$urls[] = [
        'loc' => \$permalink,
        'lastmod' => date('Y-m-d', strtotime(\$post['post_modified'])),
    ];
}

echo json_encode([
    'domain' => '$SITEMAPHOST_DOMAIN',
    'sizeLimit' => 'gsc-optimized',
    'urls' => \$urls,
]);
" > /tmp/sitemap-payload.json

echo "Pushing $(cat /tmp/sitemap-payload.json | php -r 'echo count(json_decode(file_get_contents("php://stdin"), true)["urls"]);') URLs to SitemapHost..."

curl -s -X POST https://dash.sitemaphost.app/api/sitemap/generate \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $SITEMAPHOST_API_KEY" \
  -d @/tmp/sitemap-payload.json

rm /tmp/sitemap-payload.json
echo ""
echo "Done!"

Run it:

chmod +x push-sitemap.sh
./push-sitemap.sh

Schedule with cron

Add a cron job to run the script periodically:

# Run every 6 hours
0 */6 * * * cd /var/www/html && ./push-sitemap.sh >> /var/log/sitemap-push.log 2>&1

Option 2: PHP Helper Function

For more control, add a PHP function to your theme's functions.php or a custom plugin:

<?php
/**
 * Push all published URLs to SitemapHost
 */
function sitemaphost_push_urls() {
    $api_key = defined('SITEMAPHOST_API_KEY')
        ? SITEMAPHOST_API_KEY
        : 'sk_live_xxxxxxxxxxxxxxxx';
    $domain = defined('SITEMAPHOST_DOMAIN')
        ? SITEMAPHOST_DOMAIN
        : 'sitemap.yoursite.com';

    $urls = [];

    // Get all published pages
    $pages = get_posts([
        'post_type'   => 'page',
        'post_status' => 'publish',
        'numberposts' => -1,
    ]);

    foreach ($pages as $page) {
        $urls[] = [
            'loc'     => get_permalink($page->ID),
            'lastmod' => get_the_modified_date('Y-m-d', $page),
        ];
    }

    // Get all published posts
    $posts = get_posts([
        'post_type'   => 'post',
        'post_status' => 'publish',
        'numberposts' => -1,
    ]);

    foreach ($posts as $post) {
        $urls[] = [
            'loc'     => get_permalink($post->ID),
            'lastmod' => get_the_modified_date('Y-m-d', $post),
        ];
    }

    // Include custom post types
    $custom_types = get_post_types(['public' => true, '_builtin' => false]);
    foreach ($custom_types as $type) {
        $custom_posts = get_posts([
            'post_type'   => $type,
            'post_status' => 'publish',
            'numberposts' => -1,
        ]);

        foreach ($custom_posts as $post) {
            $urls[] = [
                'loc'     => get_permalink($post->ID),
                'lastmod' => get_the_modified_date('Y-m-d', $post),
            ];
        }
    }

    // Push to SitemapHost
    $response = wp_remote_post(
        'https://dash.sitemaphost.app/api/sitemap/generate',
        [
            'headers' => [
                'Content-Type' => 'application/json',
                'X-API-Key'    => $api_key,
            ],
            'body' => json_encode([
                'domain'    => $domain,
                'sizeLimit' => 'gsc-optimized',
                'urls'      => $urls,
            ]),
            'timeout' => 30,
        ]
    );

    if (is_wp_error($response)) {
        error_log('SitemapHost push failed: ' . $response->get_error_message());
        return false;
    }

    $body = json_decode(wp_remote_retrieve_body($response), true);
    error_log('SitemapHost: Pushed ' . count($urls) . ' URLs');

    return $body;
}

Define constants in wp-config.php

// wp-config.php
define('SITEMAPHOST_API_KEY', 'sk_live_xxxxxxxxxxxxxxxx');
define('SITEMAPHOST_DOMAIN', 'sitemap.yoursite.com');

Option 3: Webhook on Publish

Automatically push to SitemapHost every time a post is published or updated:

<?php
/**
 * Push sitemap to SitemapHost when a post is published or updated.
 * Add this to functions.php or a custom plugin.
 */
add_action('publish_post', 'sitemaphost_on_publish', 10, 2);
add_action('publish_page', 'sitemaphost_on_publish', 10, 2);

function sitemaphost_on_publish($post_id, $post) {
    // Avoid triggering on autosaves or revisions
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
    if (wp_is_post_revision($post_id)) return;

    // Schedule a full sitemap push (debounced with a transient)
    if (false === get_transient('sitemaphost_push_pending')) {
        set_transient('sitemaphost_push_pending', true, 60);

        // Schedule the push for 60 seconds from now to batch rapid edits
        wp_schedule_single_event(time() + 60, 'sitemaphost_do_push');
    }
}

add_action('sitemaphost_do_push', 'sitemaphost_push_urls');

This approach debounces rapid edits -- if you publish 5 posts in quick succession, it only pushes once.

Named Sitemaps for WordPress

Organize your WordPress URLs into separate sitemaps by content type:

<?php
function sitemaphost_push_named_sitemaps() {
    $api_key = SITEMAPHOST_API_KEY;
    $domain  = SITEMAPHOST_DOMAIN;

    // Push pages sitemap
    $pages = get_posts(['post_type' => 'page', 'post_status' => 'publish', 'numberposts' => -1]);
    sitemaphost_push('pages', array_map(function($p) {
        return ['loc' => get_permalink($p->ID), 'lastmod' => get_the_modified_date('Y-m-d', $p)];
    }, $pages));

    // Push blog sitemap
    $posts = get_posts(['post_type' => 'post', 'post_status' => 'publish', 'numberposts' => -1]);
    sitemaphost_push('blog', array_map(function($p) {
        return ['loc' => get_permalink($p->ID), 'lastmod' => get_the_modified_date('Y-m-d', $p)];
    }, $posts));

    // Push products sitemap (WooCommerce)
    if (post_type_exists('product')) {
        $products = get_posts(['post_type' => 'product', 'post_status' => 'publish', 'numberposts' => -1]);
        sitemaphost_push('products', array_map(function($p) {
            $images = [];
            $thumb_id = get_post_thumbnail_id($p->ID);
            if ($thumb_id) {
                $images[] = [
                    'loc'   => wp_get_attachment_url($thumb_id),
                    'title' => get_post_meta($thumb_id, '_wp_attachment_image_alt', true),
                ];
            }
            return [
                'loc'     => get_permalink($p->ID),
                'lastmod' => get_the_modified_date('Y-m-d', $p),
                'images'  => $images ?: null,
            ];
        }, $products));
    }
}

function sitemaphost_push($sitemap_name, $urls) {
    wp_remote_post('https://dash.sitemaphost.app/api/sitemap/generate', [
        'headers' => [
            'Content-Type' => 'application/json',
            'X-API-Key'    => SITEMAPHOST_API_KEY,
        ],
        'body' => json_encode([
            'domain'      => SITEMAPHOST_DOMAIN,
            'sitemapName' => $sitemap_name,
            'sizeLimit'   => 'gsc-optimized',
            'urls'        => array_values($urls),
        ]),
        'timeout' => 30,
    ]);
}

Disabling Built-In WordPress Sitemaps

Disable WordPress Core Sitemaps (WP 5.5+)

Add to your functions.php:

// Disable WordPress core sitemaps entirely
add_filter('wp_sitemaps_enabled', '__return_false');

Disable Yoast SEO Sitemaps

If you use Yoast SEO, disable its sitemap generation:

  1. Go to Yoast SEO > Settings > Site Features
  2. Toggle XML sitemaps to Off

Or programmatically:

// Disable Yoast sitemaps
add_filter('wpseo_sitemaps_enabled', '__return_false');

Disable Rank Math Sitemaps

// Disable Rank Math sitemaps
add_filter('rank_math/sitemap/enabled', '__return_false');

Disable All in One SEO Sitemaps

Go to All in One SEO > Sitemaps and toggle the sitemap feature off.

Update robots.txt

Point your robots.txt to your SitemapHost URL. You can do this via WordPress:

// functions.php
add_filter('robots_txt', function($output, $public) {
    $output .= "\nSitemap: https://sitemap.yoursite.com/sitemap.xml\n";
    return $output;
}, 10, 2);

Or edit your physical robots.txt file:

User-agent: *
Allow: /

Sitemap: https://sitemap.yoursite.com/sitemap.xml

Headless WordPress

If you use WordPress as a headless CMS with a separate frontend (Next.js, Gatsby, Nuxt), the sitemap URLs should point to your frontend domain, not the WordPress admin URL.

<?php
// For headless WordPress: override the permalink base
define('SITEMAPHOST_FRONTEND_URL', 'https://yoursite.com');

function sitemaphost_headless_push() {
    $frontend_url = SITEMAPHOST_FRONTEND_URL;

    $posts = get_posts([
        'post_type'   => 'post',
        'post_status' => 'publish',
        'numberposts' => -1,
    ]);

    $urls = array_map(function($post) use ($frontend_url) {
        // Build frontend URL from slug instead of using get_permalink()
        return [
            'loc'     => $frontend_url . '/blog/' . $post->post_name,
            'lastmod' => get_the_modified_date('Y-m-d', $post),
        ];
    }, $posts);

    // Push to SitemapHost
    wp_remote_post('https://dash.sitemaphost.app/api/sitemap/generate', [
        'headers' => [
            'Content-Type' => 'application/json',
            'X-API-Key'    => SITEMAPHOST_API_KEY,
        ],
        'body' => json_encode([
            'domain'    => SITEMAPHOST_DOMAIN,
            'sizeLimit' => 'gsc-optimized',
            'urls'      => $urls,
        ]),
        'timeout' => 30,
    ]);
}

Next Steps