Warning: Altis v7 is no longer supported.

XML Sitemaps

XML Sitemaps are a means of providing key content and additional meta data around the frequency of updates on your website to search engines.

The sitemaps follow the specification detailed on https://www.sitemaps.org/ and are supported by Google and all major search engines.

The sitemap index can hold a maximum of 50000 sitemaps, and a single sitemap can hold a (filterable) maximum of 2000 entries.

By default, sitemaps are created for all public and publicly queryable post types and taxonomies, as well as for author archives and of course the homepage of the site if set to show posts.

A sitemap index is automatically linked to in the site's robots.txt file at /wp-sitemap.xml so you don't need to manually submit them to the Google Search Console. You can resubmit them from the search console and get diagnostic information there at any time.

Note: Sitemaps are only linked to in the robots.txt file if the site is public and in production. To debug locally you will need to define the constant HM_DISABLE_INDEXING as false.

Google Site Verification

It is necessary to verify the site with Google Search Console before you can access information about your site's search results performance. It is recommended to use the HTML file upload solution by committing the file to your project's root directory, although it is possible to add the meta tag by filling in the verification code on the Reading Settings page in the admin.

Adding Custom Sitemaps

Sitemaps are provided for built-in content types like pages and author archives out of the box. If you want to add a custom sitemap with additional features, such as a video sitemap, you can register a custom sitemap provider.

To do so, create a custom PHP class that extends the abstract WP_Sitemaps_Provider class. Then you can use the wp_register_sitemap_provider() function to register it.

The example below shows a minimal implementation for a custom video sitemap:

class Video_Sitemap_Provider extends WP_Sitemaps_Provider {

	/**
	 * Set the name and object type properties. Required.
	 */
	public function __construct() {
		$this->name = 'videos';
		$this->object_type = 'post';
	}

	/**
	 * Return the list of URLs for the current page.
	 */
	public function get_url_list( $page ) {
		$videos = new WP_Query( [
			'post_type' => 'video',
			// phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_numberposts
			'posts_per_page' => 2000,
			'fields' => 'ids',
			'paged' => $page,
		] );

		$urls = [];

		foreach ( $videos->posts as $video_id ) {
			$urls[] = get_post_meta( $video_id, 'video_url', true );
		}

		return $urls;
	}

	/**
	 * Return the maximum number of pages to output for this sitemap.
	 */
	public function get_max_num_pages() {
		$videos = new WP_Query( [
			'post_type' => 'video',
			// phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_numberposts
			'posts_per_page' => 2000,
			'fields' => 'ids',
		] );

		return $videos->max_num_pages;
	}

}

add_filter( 'init', function () : void {
	$provider = new Video_Sitemap_Provider();
	wp_register_sitemap_provider( 'video-sitemaps', $provider );
} );

The provider will be responsible for getting all sitemaps and sitemap entries, as well as determining pagination.

Removing Specific Sitemaps

There are three existing sitemaps providers for standard object types - posts, taxonomies, and users. If you want to remove one of them such as the "users" provider, you can configure the module like so:

{
	"extra": {
		"altis": {
			"modules": {
				"seo": {
					"xml-sitemaps": {
						"users": false
					}
				}
			}
		}
	}
}

Alternatively, if you need to use more complex logic or work with custom sitemap providers you can use the wp_sitemaps_add_provider filter to do so. For example:

add_filter( 'wp_sitemaps_add_provider', function ( $provider, string $name ) {
	// Only switch off the user sitemaps for subsites on the network.
	if ( $name === 'users' && ! is_main_site() ) {
		return false;
	}

	return $provider;
}, 10, 2 );

If you want to disable sitemap generation for a specific post type or taxonomy, use the wp_sitemaps_post_types or wp_sitemaps_taxonomies filter, respectively.

To disable sitemaps for the page post type you would do the following:

add_filter( 'wp_sitemaps_post_types', function ( array $post_types ) : array {
	unset( $post_types['page'] );
	return $post_types;
} );

To disable sitemaps for the post_tag taxonomy:

add_filter( 'wp_sitemaps_taxonomies', function ( array $taxonomies ) : array {
	unset( $taxonomies['post_tag'] );
	return $taxonomies;
} );

Adding Additional Tags to Sitemap Entries

The sitemaps protocol specifies a certain set of supported attributes for sitemap entries. Of those, only the loc tag (URL) is required. All others (e.g. changefreq and priority) are optional tags in the sitemaps protocol and not typically consumed by search engines, which is why only the URL itself is listed by default. You can still add those tags if you need to.

You can use the wp_sitemaps_posts_entry, wp_sitemaps_users_entry or wp_sitemaps_taxonomies_entry filters to add additional tags like changefreq, priority, or lastmod to single items in the sitemap.

To add the last modified date for posts:

add_filter( 'wp_sitemaps_posts_entry', function( array $entry, WP_Post $post ) : array {
	$entry['lastmod'] = $post->post_modified_gmt;
	return $entry;
}, 10, 2 );

Similarly, you can use the wp_sitemaps_index_entry filter to add lastmod on the sitemap index.

Trying to add any unsupported tags will result in a PHP notice.

Excluding a Single Post from the Sitemap

If you have a feature that allows setting specific posts or pages to noindex, it's a good idea to exclude those from the sitemap too.

The wp_sitemaps_posts_query_args filter can be used to exclude specific posts from the sitemap. Here's an example:

add_filter( 'wp_sitemaps_posts_query_args', function ( array $args, string $post_type ): array {
	if ( 'post' !== $post_type ) {
		return $args;
	}

	$args['post__not_in'] = isset( $args['post__not_in'] ) ? $args['post__not_in'] : [];
	$args['post__not_in'][] = 123; // 123 is the ID of the post to exclude.
	return $args;
}, 10, 2 );