Extending and Customizing the XML Sitemap in Magento 2
AI generated
M2
di.xml
Magento 2
Extending and Customizing the XML Sitemap
Registering custom URL types, controlling priority, and automating submission

Magento's sitemap module already covers products, categories and CMS pages, but there is no ready made item provider for custom landing pages or other project specific URL types. This article shows how to register a custom item provider, how to control priority and change frequency per content type, and how to automate multi store sitemap generation together with submission to Google Search Console.

10 min read Sitemap ItemProviderInterface Multi store Search Console

1. What the default sitemap covers, and what it does not

The native Magento sitemap covers three content types: products, categories and CMS pages, each through its own item provider. Anything outside those three types, such as standalone landing pages from a custom module, brand overview pages or generated PDF guide pages, gets no automatic entry in the sitemap.

That clearly sets this topic apart from URL rewrites, covered elsewhere. A URL rewrite makes sure a page is reachable under a readable URL at all, but says nothing about whether that URL also shows up in the sitemap and therefore gets actively offered to search engines for crawling.

Anyone introducing new URL types, for example as part of a custom content module, should think from the start about how those URLs reach the sitemap, instead of relying purely on organic crawling without a sitemap entry.

2. The item provider architecture of the sitemap module

The central piece is the ItemProviderInterface with a getItems method that returns a list of SitemapItemInterface objects. Each object carries a URL, a modification date, a priority and a change frequency. All registered providers get merged through ItemProviderComposite, which is itself just a simple aggregator adding up the results from every configured provider into one list.

New providers get registered declaratively through di.xml, by adding an entry to the itemProviders array on the virtual type of ItemProviderComposite. Important in day to day work: after changing that array, setup:di:compile has to run, sometimes even twice, since an already compiled container can otherwise silently ignore the new entry, even in developer mode.


<virtualType name="Magento\Sitemap\Model\ItemProvider\ItemProviderComposite">
    <arguments>
        <argument name="itemProviders" xsi:type="array">
            <item name="category" xsi:type="object">Magento\Sitemap\Model\ItemProvider\CategoryItemProvider</item>
            <item name="product" xsi:type="object">Magento\Sitemap\Model\ItemProvider\ProductItemProvider</item>
            <item name="cms_page" xsi:type="object">Magento\Sitemap\Model\ItemProvider\CmsPageItemProvider</item>
            <item name="landingpage" xsi:type="object">Mironsoft\CustomSitemap\Model\ItemProvider\LandingPageItemProvider</item>
        </argument>
    </arguments>
</virtualType>

3. Implementing a custom item provider for landing pages

A custom provider reads its own landing page entity, filters for active entries approved for search, and builds SitemapItemInterface objects with a complete, absolute URL from that data. It matters that the modification dates come from the entity's actual updated_at column rather than a static timestamp, so the sitemap honestly reflects when a page last changed.

Priority and change frequency should not be hard coded but configurable through system.xml, mirroring the fields Magento already offers for products, categories and CMS pages under Stores, Configuration, XML Sitemap. That allows adjusting how new URL types get weighted later on without a code deploy.


<?php

declare(strict_types=1);

namespace Mironsoft\CustomSitemap\Model\ItemProvider;

use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Sitemap\Model\ItemProvider\ItemProviderInterface;
use Magento\Sitemap\Model\SitemapItemInterfaceFactory;
use Mironsoft\CustomSitemap\Model\ResourceModel\LandingPage\CollectionFactory;

/**
 * Provides active custom landing pages as additional entries for the XML sitemap.
 */
class LandingPageItemProvider implements ItemProviderInterface
{
    private const XML_PATH_PRIORITY = 'sitemap/landingpage/priority';
    private const XML_PATH_CHANGEFREQ = 'sitemap/landingpage/changefreq';

    /**
     * @param CollectionFactory $collectionFactory
     * @param SitemapItemInterfaceFactory $itemFactory
     * @param ScopeConfigInterface $scopeConfig
     */
    public function __construct(
        private readonly CollectionFactory $collectionFactory,
        private readonly SitemapItemInterfaceFactory $itemFactory,
        private readonly ScopeConfigInterface $scopeConfig,
    ) {
    }

    /**
     * Builds sitemap entries for every active landing page of the given store.
     *
     * @param int $storeId
     * @return \Magento\Sitemap\Model\SitemapItemInterface[]
     */
    public function getItems($storeId): array
    {
        $priority = (float) $this->scopeConfig->getValue(self::XML_PATH_PRIORITY, 'store', $storeId);
        $changeFreq = (string) $this->scopeConfig->getValue(self::XML_PATH_CHANGEFREQ, 'store', $storeId);

        $collection = $this->collectionFactory->create();
        $collection->addFieldToFilter('is_active', ['eq' => 1])
            ->addFieldToFilter('store_id', ['eq' => $storeId]);

        $items = [];
        foreach ($collection as $landingPage) {
            $items[] = $this->itemFactory->create([
                'url' => $landingPage->getUrlKey(),
                'updatedAt' => $landingPage->getUpdatedAt(),
                'images' => [],
                'priority' => $priority,
                'changeFrequency' => $changeFreq,
            ]);
        }

        return $items;
    }
}

4. Making priority and change frequency configurable per content type

For the configurable values, it is worth adding a dedicated system.xml section inside the existing XML Sitemap group in the admin, with select fields for change frequency, mirroring the values always, hourly, daily, weekly, monthly, yearly and never that the sitemap standard already defines. Priority gets maintained as a decimal value between zero and one.

In practice, it has proven useful to give landing pages with time limited campaign relevance a higher priority and a shorter change frequency than evergreen content, so search engines get a hint to recrawl those pages more often, even though priority and change frequency ultimately only count as a signal for search engines, not a guarantee.

5. Configuring multi store sitemap generation

Every sitemap configuration in Magento is bound to a store, so every store that needs its own sitemap requires its own entry under Marketing, SEO and Search, Site Map, with its own file name and path. A common mistake is reusing the same file name across multiple stores, which makes the generated files overwrite each other.

The custom LandingPageItemProvider from the previous section has to filter store aware without exception, otherwise landing pages from one store wrongly show up in another store's sitemap as well, especially when several stores share the same database table with a store_id column.

The generate_sitemap cron job runs for all configured sitemaps in a single pass, which in practice keeps the timing offset between several store sitemaps down to a few seconds, consistent enough for most projects.

6. Automating submission to Google Search Console

Magento reliably generates the sitemap file, but it does not automatically submit it to Google Search Console, that stays a manual step in the Search Console interface by default. For automated submission, an additional cron job works well, calling the Search Console API with service account access after a successful sitemap run and reporting the generated URL through the sitemaps.submit endpoint.

The service account needs access rights on the relevant property verified in Search Console, usually through delegated access with a Google Cloud service identity created specifically for this purpose. This automation pays off especially when landing pages get created frequently and manually resubmitting after every campaign would be impractical.


# Trigger sitemap generation, then submit through a dedicated cron script
bin/magento sitemap:generate

# custom cron job then calls the Search Console API, e.g.
curl -X PUT \
  "https://www.googleapis.com/webmasters/v3/sites/https%3A%2F%2Fwww.example.com%2F/sitemaps/sitemap.xml" \
  -H "Authorization: Bearer $SEARCH_CONSOLE_TOKEN"

The core sitemap module ships with no automatic hreflang alternate links between language variants of the same page, even though that is exactly what international multi store setups often want. That gap can be closed through a custom provider or a plugin on the existing providers that attaches additional alternate entries for the other language stores to each URL.

It matters that the mapping between language variants relies on a stable, language independent identifier, such as a shared landing page group ID, rather than a purely pattern based URL derivation, since translated slugs often differ substantially between languages and cannot be mapped into each other automatically.

8. Validating and monitoring the generated sitemap

After every extension with a new item provider, it is worth checking the generated file against Google's limits, a maximum of fifty thousand URLs or fifty megabytes uncompressed per file. Magento automatically splits into multiple files with a parent sitemap index once those limits get exceeded, but a custom provider adding many extra URLs can trigger that split point earlier than expected.

For ongoing operation, a simple monitor on the generate_sitemap cron job through the standard cron history in the admin is worth setting up, complemented by a check that the most recently generated file actually contains entries of the new content type. A silent failure in a custom provider, such as an empty collection caused by a wrong filter, otherwise produces a seemingly successful but content wise incomplete sitemap.

9. Pitfalls from real projects

The most common pitfall is a forgotten setup:di:compile after extending the itemProviders array, which means the new provider simply never gets called despite a correct di.xml configuration, sometimes even in developer mode, since already compiled container definitions can silently ignore the new entry. In practice it helps to run the compile step twice after such a change, just to be safe.

A second common mistake is missing store filtering in a custom provider, which causes landing pages from one store to accidentally appear in every store's sitemap. A third pitfall concerns forgotten deactivation logic, when a landing page gets deactivated or deleted in the admin, the next sitemap run has to automatically remove that URL, otherwise dead links remain in the sitemap and get flagged as crawl errors by search engines.

URL type Item provider Priority configurable? Multi store capable?
Products ProductItemProvider (core) Yes, via XML Sitemap configuration Yes, natively
Categories CategoryItemProvider (core) Yes, via XML Sitemap configuration Yes, natively
CMS pages CmsPageItemProvider (core) Yes, via XML Sitemap configuration Yes, natively
Custom landing pages Custom provider (this article) Yes, via dedicated system.xml fields Yes, with explicit store filtering
Hreflang alternates Custom plugin on existing providers Not separately, follows the base URL Yes, central for linking language stores

Mironsoft

Magento development, module consulting, and system architecture

A Magento project that needs a second opinion or experienced execution?

We build custom Magento modules, advise on architecture decisions, and take on complex implementations, from service contract planning to production-ready deployment.

Architecture Consulting

Have module and system architecture thought through properly before you build.

Custom Module Development

Build custom Magento modules cleanly, following best practices.

Code Review & Audit

Have existing modules reviewed for performance, security, and maintainability.

10. Summary

XML sitemap

Architecture

ItemProviderInterface implementations supply sitemap items, ItemProviderComposite merges every registered provider.

Extension

New URL types get added through the itemProviders array in di.xml, after which setup:di:compile is mandatory.

Configuration

Priority and change frequency should be configurable through dedicated system.xml fields instead of being hard coded.

Automation

An additional cron job can automatically submit the generated sitemap through the Search Console API.

11. FAQ: XML sitemap

1Which interface does a custom sitemap provider need to implement?
ItemProviderInterface with a getItems method that returns a list of SitemapItemInterface objects for a given store.
2Is a di.xml change alone enough for a new provider to take effect?
No, setup:di:compile has to run afterward, sometimes even twice, or the new entry in the itemProviders array gets silently ignored.
3Does the default sitemap also cover custom content types such as landing pages?
No, the default only covers products, categories and CMS pages, custom content types need their own item provider.
4Where should priority and change frequency for a custom provider come from?
From configurable system.xml fields rather than hard coded values, so the weighting can be adjusted without a code deploy.
5Can a single sitemap file be shared across multiple stores?
Not sensibly, every store with its own sitemap needs its own sitemap entry with its own file name, otherwise generated files overwrite each other.
6Does Magento support hreflang alternate links in the sitemap natively?
No, that has to be added through a custom provider or plugin that maps URLs using a language independent identifier.
7What happens when a sitemap exceeds the fifty thousand URL limit?
Magento automatically splits into multiple files with a parent sitemap index, a custom provider can trigger that split point earlier than expected.
8Does Magento automatically submit a new sitemap to Google Search Console?
No, that stays a manual step by default, automation requires an additional cron job with Search Console API access.
9What happens when a landing page gets deactivated?
The next sitemap run has to automatically remove its URL, otherwise dead links remain and get flagged as crawl errors by search engines.
10Why should a custom provider filter store aware?
Without explicit store filtering, landing pages from one store can wrongly show up in another store's sitemap as well.