Question: Howto add real last modified date in sitemap.xml

Hi all!

I wonder if you could help me out here with the questions below.

We have an internal NuGet package for exposing the sitemap.xml in a generic way (based on given Reusable field schema(s)). It displays everything with their alternate url's, a snippet here:

  <url>
    <loc>https://test.xbyk.local:44395/en/departments</loc>
    <xhtml:link rel="alternate" hreflang="en" href="https://test.xbyk.local:44395/en/departments" />
    <xhtml:link rel="alternate" hreflang="nl" href="https://test.xbyk.local:44395/afdelingen" />
    <lastmod>2026-06-19T18:38:12+02:00</lastmod>
  </url>

One thing that hasn't been done yet is adding the true lastmod. Since most pages are also build up using contentitems (selected on page and/or widgets) quite often the lastmod date is not the actual lastmod date, because a contentitem on it has changed.
We need this true date because of external access, and google of course.

Question: Has anyone resolved this issue already? Or might Kentico be working on this (is it on any roadmap?)

Trying to retrieve the true last modified date during generating the sitemap.xml is not an option. For thousands of pages that would be to slow and heavy. I thought I might solve it in the the following way:

  1. Create a Custom Module and Data Class
  • Create a custom module in Xperience by Kentico.

  • Within this module, create a data class (e.g., PageContentLastModified) with fields like WebPageItemID and ContentLastModified (datetime).

  1. Update the Value via Event Handlers
  • Implement an event handler that triggers when a related content item is published.

  • In this handler, retrieve all related pages using this contentitem (recursively, if needed).

  • Store this date in your custom module table for the corresponding WebPageItemID.

  1. Use This Value in Your sitemap.xml
  • When generating the sitemap.xml, read the last content modification date from your custom module table and use the latest date: page or content.

  • If no value is stored, fall back to the page’s standard LastModified date. Initially this date is good anyways, until there is something changed.

Question: Anybody any idea's on this? Good/bad or a better suggestion?


Answers

This is an interesting solution to your problem, but I don't think it is possible to achieve this 100%. Here are some scenarios and product features that complicate things:

  • Smart folders as a content source for Page Builder components

    • Items move in and out of smart folders as they change or the smart folder filter rules change
  • Code-based content selection logic

    • Could be driven by search indexes or simple database querying
  • Taxonomy as a content source for web pages

    • As items add/remove tags they will be included/excluded in query results

I'm sure there are several other less frequent examples, but the common thread among all of them is content displayed that isn't from the web page itself and isn't explicitly selected by a content marketer through a linked item.

The flexibility of querying for related content via the approaches mentioned above means you can't know what content is being displayed at any given moment without running the query itself. This also means you can't know when every piece of that web page, represented by other content items, has updated.

If this sitemap date accuracy is critical for you, you could try getting the content item information from the web page and working backwards.

  • Any time a content item contributes to the content or experience of a page, include its modification date in a request scoped and concurrent data store.
  • At the end of the page, after all content has been rendered, embed this date value in the HTML as metadata.
  • Run a scheduled task that scrapes the pages on the site and extracts this metadata.
  • Store the webpage and date metadata in a custom object type and use that to drive the sitemap.
Accepted answer

This is how I would resolve it:

  1. Get all the ContentItemID to Last published When:
    select ContentItemLanguageMetadataContentItemID, MAX(ContentItemLanguageMetadataScheduledPublishWhen) from CMS_ContentItemLanguageMetadata group by ContentItemLanguageMetadataContentItemID

    1. This will grab the latest across the languages...may take more logic to get it per language if you really want to try to handle that
  2. Get a dictionary of all the ContentItemID to the list related ContentItemIDs from the CMS_ContentItemReference table (joining to the CMS_ContentItemCommonData):

    SELECT Parent.ContentItemCommonDataContentItemID, Child.ContentItemCommonDataContentItemID
    FROM [dbo].[CMS_ContentItemReference]
    inner join CMS_ContentItemCommonData Parent on Parent.ContentItemCommonDataID = ContentItemReferenceSourceCommonDataID
    inner join CMS_ContentItemCommonData Child on Child.ContentItemCommonDataID = ContentItemReferenceTargetItemID
    group by Parent.ContentItemCommonDataContentItemID, Child.ContentItemCommonDataContentItemID
    order by Parent.ContentItemCommonDataContentItemID

  3. For each parent ContentItemID you want to find the last modified, create a recursive lookup method that takes these 2 dictionaries and the ref of the 'current last modified date' and as it scans children, updates the last modified date if it finds one newer than the current one (i would also pass a level so you have max level in case any recursion, OR a reference to 'already referenced content item IDs' to not check those again)

  4. This should produce the actual last modified date.

May take some additional logic, but as long as you only do those 2 queries, which are both very SMALL amounts of data, it should be relatively quick to parse through them. Doing a frozen dictionary may also improve speeds.

Also, make sure you CACHE the result per content ItemID (and separately cache those 2 dictionaries), just in case this gets hit multiple times or what not. Putting it in a scheduled task to generate a custom module class would also be fine, but this would be the logic i would go with to determine the value.

To response this discussion, you have to login first.