How to Build a Custom XML Sitemap in WordPress

WordPress Custom Sitemap
SEO & Digital Marketing Consultant » Technical » How to Build a Custom XML Sitemap in WordPress

XML sitemaps are important for SEO, it provides a list of pages for search engines to crawl from your website. They can also be used to help inform about the structure of your content.

There are plugins out there such as Yoast SEO that can generate them automatically in WordPress but there might be certain cases where you need to build a custom sitemap. For example, you might have hosting restrictions, specific URL requirements, or using a reverse proxy.

This guide explains how to create a custom XML sitemap in WordPress for a reverse proxy use case but can be adapted for other cases too…


Step 1: Intercept Sitemap Requests

Intercept requests for custom sitemap URLs (e.g., /custom-sitemap_index.xml) and generate the required XML directly. Use the init hook to catch the requests early.

add_action('init', function () {
    if (isset($_SERVER['REQUEST_URI'])) {
        $custom_sitemaps = [
            '/custom-sitemap_index.xml' => 'index',
            '/custom-post-sitemap.xml' => 'post',
            '/custom-page-sitemap.xml' => 'page',
            // Additional cases for categories and authors...
        ];

        foreach ($custom_sitemaps as $path => $type) {
            if (strpos($_SERVER['REQUEST_URI'], $path) === 0) {
                if ($type === 'index') {
                    generate_custom_sitemap_index();
                } else {
                    generate_custom_sitemap($type);
                }
                exit;
            }
        }
    }
});

This ensures WordPress processes the correct XML before default templates or redirects interfere.


Step 2: Generate the Sitemap Index

The sitemap index acts as a directory for individual content sitemaps (e.g., posts, pages, categories).

function generate_custom_sitemap_index() {
    $sitemaps = [
        'custom-post-sitemap.xml'     => $wpdb->get_var("SELECT MAX(post_modified_gmt) FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_type = 'post'"),
        'custom-page-sitemap.xml'     => $wpdb->get_var("SELECT MAX(post_modified_gmt) FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_type = 'page'"),
'custom-post-sitemap.xml' => '2025-01-31T15:44:00+00:00',
        // Additional cases for categories and authors...
    ];

    header('Content-Type: application/xml; charset=utf-8');
    echo '<?xml version="1.0" encoding="UTF-8"?>';
    echo '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';

    foreach ($sitemaps as $slug => $lastmod) {
        $url = site_url($slug);  
        echo "<sitemap>";
        echo "<loc>$url</loc>";
        echo "<lastmod>$lastmod</lastmod>";
        echo "</sitemap>";
    }

    echo '</sitemapindex>';
    exit;
}

Step 3: Create Content-Specific Sitemaps

For each content type (e.g., posts, pages), generate a corresponding XML file. Replace the domain in URLs when needed, such as for reverse proxies.

function generate_custom_sitemap($type) {
    global $wpdb;

    header('Content-Type: application/xml; charset=utf-8');
    echo '<?xml version="1.0" encoding="UTF-8"?>';
    echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';

    $site_url = get_site_url();  

    switch ($type) {
        case 'post':
            $posts = $wpdb->get_results("SELECT ID, post_modified_gmt FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_type = 'post'");
            foreach ($posts as $post) {
                $url = str_replace($site_url, 'https://proxy-domain.com', get_permalink($post->ID));
                $lastmod = gmdate('Y-m-d\TH:i:s+00:00', strtotime($post->post_modified_gmt));
                echo "<url><loc>$url</loc><lastmod>$lastmod</lastmod></url>";
            }
            break;

        case 'page':
            $pages = $wpdb->get_results("SELECT ID, post_modified_gmt FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_type = 'page'");
            foreach ($pages as $page) {
                $url = str_replace($site_url, 'https://proxy-domain.com', get_permalink($page->ID));
                $lastmod = gmdate('Y-m-d\TH:i:s+00:00', strtotime($page->post_modified_gmt));
                echo "<url><loc>$url</loc><lastmod>$lastmod</lastmod></url>";
            }
            break;

        // Additional cases for categories and authors...
    }

    echo '</urlset>';
    exit;
}

Step 4: Prevent Trailing Slash Issues

To avoid WordPress redirecting sitemap URLs to versions with trailing slashes, disable canonical redirects for sitemap paths.

add_filter('redirect_canonical', function ($redirect_url, $requested_url) {
    $sitemap_paths = [
        'custom-sitemap_index.xml',
        'custom-post-sitemap.xml',
        'custom-page-sitemap.xml',
        // Additional cases for categories and authors...
    ];

    foreach ($sitemap_paths as $path) {
        if (strpos($requested_url, $path) !== false) {
            return false; // Disable redirect
        }
    }

    return $redirect_url;
}, 10, 2);

Step 5: Add Rewrite Rules

Register custom rewrite rules to make WordPress recognise sitemap URLs.

function add_custom_sitemap_rewrite_rules() {
    add_rewrite_rule('^custom-sitemap_index\.xml$', 'index.php', 'top');
    add_rewrite_rule('^custom-post-sitemap\.xml$', 'index.php', 'top');
    add_rewrite_rule('^custom-page-sitemap\.xml$', 'index.php', 'top');
    // Additional cases for categories and authors...
}
add_action('init', 'add_custom_sitemap_rewrite_rules');

Flush the rewrite rules after activation by visiting Settings > Permalinks and clicking Save Changes.


Testing

Once the plugin is activated, verify the following URLs:

  • /custom-sitemap_index.xml
  • /custom-post-sitemap.xml
  • /custom-page-sitemap.xml

Check the <loc> tags in the XML files to ensure the URLs are correct, especially if a reverse proxy is in use.

About the author

Jonathan Stringer

With over 20 years of SEO experience across eCommerce, travel, real estate, and B2B sectors, Jonathan has led teams in-house, at agencies, and as a consultant. His expertise lies in technical SEO, data-driven audits, and scalable strategies.

View all articles by Jonathan Stringer

Recent articles

Content

Optimise content with a priority-first workflow

Practical, step-by-step guide on how to optimise content for SEO and user intent. Prioritise updates, improve structure, meta tags, links and measure impact.

Content, Distribution & Reputation

What happened to infographics and SEO?

A concise guide to why infographics no longer deliver the same SEO returns and what to do instead. Practical steps for creation, optimisation and…

Distribution & Reputation, Marketing

Entity-based SEO and the knowledge graph

A practical guide to using the knowledge graph in entity-based SEO. Prioritised audits, schema/JSON-LD, entity linking and measurement steps you can apply.

Distribution & Reputation, SEO & AI

How digital PR helps brands appear in AI overviews

Practical guide to using digital PR to increase the chance your brand is cited in Google AI Overviews. Tactics, a prioritised checklist and measurement…