Handle content events
Content events allow you to execute custom code when actions occur during the lifecycle of content items, website channel pages, and headless items. These events are triggered when content is created, updated, published, unpublished, deleted, or moved.
Content event handlers with async support
To handle a content event, create a class implementing the IAsyncEventHandler<TAsyncEvent> interface (from the CMS.Base namespace), where TAsyncEvent is one of the available content event classes.
Define the HandleAsync method in the handler class:
using System.Threading;
using System.Threading.Tasks;
using CMS.Base;
using CMS.ContentEngine;
// Handler triggered after a content item is published
public class ContentItemPublishHandler : IAsyncEventHandler<AfterPublishContentItemEvent>
{
public async Task HandleAsync(AfterPublishContentItemEvent asyncEvent, CancellationToken cancellationToken)
{
// Accesses the strongly-typed event data
PublishContentItemEventData data = asyncEvent.Data;
string contentType = data.ContentTypeName;
int contentItemId = data.ID;
// Implement custom logic, e.g., notify an external system
}
}
The HandleAsync method receives the content event object as a parameter. Access the object’s Data property to get a strongly-typed data object with properties specific to the event action.
Content events fall into two categories – compound and after-only.
Compound before/after events
Most content events come in Before and After pairs:
- Before handlers execute before the operation. You can modify mutable properties (marked with a
setaccessor) of theDataobject to influence the operation. - After handlers execute after the operation completes. Properties that are only available after the operation (such as
IDandGuidof newly created items) are populated in theDataobject at this stage.
Both the Before and After handlers of a compound event pair share the same Data object instance, as well as a State property for passing custom state. See Pass state between before and after events.
After-only events
Some content events are standalone, with no Before counterpart to the After handler. These represent irreversible or notification-only operations such as publish, unpublish, delete, and move.
After-only events do not have a State property because there is no Before handler to set state values.
Register content event handlers
Register content event handlers using the AddEventHandler<TAsyncEvent, THandler>() extension method on IServiceCollection. You can create a custom module with initialization code and access IServiceCollection in the OnPreInit override.
using CMS;
using CMS.Base;
using CMS.ContentEngine;
using CMS.Core;
using CMS.DataEngine;
[assembly: RegisterModule(typeof(ContentEventModule))]
public class ContentEventModule : Module
{
public ContentEventModule() : base(nameof(ContentEventModule))
{
}
protected override void OnPreInit(ModulePreInitParameters parameters)
{
base.OnPreInit(parameters);
// Registers a handler for the AfterPublishContentItemEvent
parameters.Services.AddEventHandler<AfterPublishContentItemEvent, ContentItemPublishHandler>();
}
}
Handlers registered via AddEventHandler are added to the dependency injection container as singletons. You can use standard constructor dependency injection to resolve services within your handler classes.
Pass state between before and after events
For compound before/after events, you can pass custom state from a Before handler to an After handler using the State property of the event object. The State property is an EventStateStore that provides a key-value store with the following methods:
SetValue<TValue>– stores a value in the Before handlerTryGetValue<TValue>– retrieves the value in the After handler
// Handler triggered before a content item's language-specific metadata is updated
public class ContentItemBeforeUpdateLanguageMetadataHandler :
IAsyncEventHandler<BeforeUpdateContentItemLanguageMetadataEvent>
{
public async Task HandleAsync(BeforeUpdateContentItemLanguageMetadataEvent asyncEvent, CancellationToken cancellationToken)
{
// Stores the original display name before the content item update,
// under the "CustomHandler.OriginalDisplayName" key
asyncEvent.State.SetValue<string>("CustomHandler.OriginalDisplayName",
asyncEvent.Data.OriginalDisplayName);
}
}
// Handler triggered after a content item's language-specific metadata is updated
public class ContentItemAfterUpdateLanguageMetadataHandler :
IAsyncEventHandler<AfterUpdateContentItemLanguageMetadataEvent>
{
public async Task HandleAsync(AfterUpdateContentItemLanguageMetadataEvent asyncEvent, CancellationToken cancellationToken)
{
// Retrieves the state value stored in the Before handler
if (asyncEvent.State.TryGetValue<string>("CustomHandler.OriginalDisplayName",
out string originalDisplayName))
{
// Gets the current display name after the update operation
string currentDisplayName = asyncEvent.Data.DisplayName;
// Compares the old and new display names
if (!string.Equals(originalDisplayName, currentDisplayName))
{
// Custom logic if the display name was changed
}
}
}
}
Use unique State keys
State key values are shared across all handlers registered for a given event class. Use namespaced keys (e.g., "CustomHandler.PropertyName") to avoid collisions between different handlers.
Content event classes
Content item events
The following events from the CMS.ContentEngine namespace cover reusable content items:
|
Action |
Event classes |
Description |
|
Create |
Data class: |
Occurs when a content item is created. |
|
Create language variant |
Data class: |
Occurs when a new language variant of a content item is created. |
|
Update draft |
Data class: |
Occurs when a content item draft is updated. |
|
Update metadata |
Data class: |
Occurs when content item metadata (e.g., the code name or security settings) is updated. |
|
Update language metadata |
Data class: |
Occurs when language-specific metadata (e.g., display name or publish/unpublish scheduling) is updated. |
|
Publish |
Data class: |
Occurs after a content item is published. After-only event. |
|
Unpublish |
Data class: |
Occurs after a content item is unpublished. After-only event. |
|
Delete |
Data class: |
Occurs after a content item is deleted. After-only event. |
Page events
The following events from the CMS.Websites namespace cover website channel pages:
|
Action |
Event classes |
Description |
|
Create |
Data class: |
Occurs when a page is created. |
|
Create language variant |
Data class: |
Occurs when a new language variant of a page is created. |
|
Create folder |
Data class: |
Occurs when a folder is created in the page tree. |
|
Create folder language variant |
Data class: |
Occurs when a new language variant of a folder is created. |
|
Update draft |
Data class: |
Occurs when a page draft is updated. |
|
Update metadata |
Data class: |
Occurs when page metadata (e.g., the code name or security settings) is updated. |
|
Update language metadata |
Data class: |
Occurs when language-specific metadata of a page (e.g., display name or publish/unpublish scheduling) is updated. |
|
Update tree path slug |
Data class: |
Occurs when a page’s URL path slug is updated. |
|
Update security settings |
Data class: |
Occurs when a page’s security settings are updated. |
|
Publish |
Data class: |
Occurs after a page is published. After-only event. |
|
Unpublish |
Data class: |
Occurs after a page is unpublished. After-only event. |
|
Move |
Data class: |
Occurs after a page is moved in the content tree. After-only event. |
|
Delete |
Data class: |
Occurs after a page is deleted. After-only event. |
Headless item events
The following events from the CMS.Headless namespace cover headless items:
|
Action |
Event classes |
Description |
|
Create |
Data class: |
Occurs when a headless item is created. |
|
Create language variant |
Data class: |
Occurs when a new language variant of a headless item is created. |
|
Update draft |
Data class: |
Occurs when a headless item draft is updated. |
|
Update metadata |
Data class: |
Occurs when headless item metadata is updated (e.g., security settings). |
|
Update language metadata |
Data class: |
Occurs when language-specific metadata of a headless item (e.g., display name) is updated. |
|
Publish |
Data class: |
Occurs after a headless item is published. After-only event. |
|
Unpublish |
Data class: |
Occurs after a headless item is unpublished. After-only event. |
|
Delete |
Data class: |
Occurs after a headless item is deleted. After-only event. |
Example – CDN cache invalidation on publish
The following example demonstrates a content event handler that invalidates a CDN cache when an article page is published on a website channel.
Open your Xperience solution in your preferred IDE.
Create a handler class within a custom assembly (Class Library project) referenced by your project:
C#using System; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using CMS.Base; using CMS.Websites; // Handler triggered after a website channel page is published public class ArticlePublishCdnHandler : IAsyncEventHandler<AfterPublishWebPageEvent> { private readonly IHttpClientFactory httpClientFactory; public ArticlePublishCdnHandler(IHttpClientFactory httpClientFactory) { this.httpClientFactory = httpClientFactory; } public async Task HandleAsync(AfterPublishWebPageEvent asyncEvent, CancellationToken cancellationToken) { PublishWebPageEventData data = asyncEvent.Data; // Only handles pages of the 'Acme.ArticlePage' content type if (!string.Equals(data.ContentTypeName, "Acme.ArticlePage", StringComparison.OrdinalIgnoreCase)) { return; } // Gets the page's tree path and website channel to build the invalidation request string treePath = data.TreePath; string channelName = data.WebsiteChannelName; HttpClient httpClient = httpClientFactory.CreateClient("CdnApi"); // Sends a cache invalidation request to the CDN await httpClient.PostAsJsonAsync( "api/cache/invalidate", new { Path = treePath, Channel = channelName }, cancellationToken); } }Create a module class and register the handler in the
OnPreInitoverride:C#using CMS; using CMS.Base; using CMS.Core; using CMS.DataEngine; using CMS.Websites; [assembly: RegisterModule(typeof(CdnInvalidationModule))] public class CdnInvalidationModule : Module { public CdnInvalidationModule() : base(nameof(CdnInvalidationModule)) { } // OnPreInit allows you to access IServiceCollection via ModulePreInitParameters protected override void OnPreInit(ModulePreInitParameters parameters) { base.OnPreInit(parameters); // Registers the event handler parameters.Services.AddEventHandler<AfterPublishWebPageEvent, ArticlePublishCdnHandler>(); } }
When an article page is published, the handler sends a cache invalidation request to an external CDN API.
Legacy content event handlers
We strongly recommend using handlers with async support for content events. The event handling approach described on this page provides the following advantages:
- Full async/await support without thread-blocking risk
- Standard constructor dependency injection
- Singleton lifecycle managed by the DI container
However, the system maintains full backward compatibility for legacy synchronous handlers using the ContentItemEvents, WebPageEvents, and HeadlessItemEvents static classes.
See Reference - Global system events for detailed information.