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:

C#
Content event handling

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 set accessor) of the Data object to influence the operation.
  • After handlers execute after the operation completes. Properties that are only available after the operation (such as ID and Guid of newly created items) are populated in the Data object 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.

C#
Register a content event handler

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 handler
  • TryGetValue<TValue> – retrieves the value in the After handler
C#
Set state in a Before 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);
    }
}
C#
Get state in an After handler

// 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

BeforeCreateContentItemEvent AfterCreateContentItemEvent

Data class: CreateContentItemEventData

Occurs when a content item is created. ID and Guid are only populated in the After event.

Create language variant

BeforeCreateLanguageVariantEvent AfterCreateLanguageVariantEvent

Data class: CreateLanguageVariantEventData

Occurs when a new language variant of a content item is created.

Update draft

BeforeUpdateDraftEvent AfterUpdateDraftEvent

Data class: UpdateDraftEventData

Occurs when a content item draft is updated.

Update metadata

BeforeUpdateContentItemMetadataEvent AfterUpdateContentItemMetadataEvent

Data class: UpdateContentItemMetadataEventData

Occurs when content item metadata (e.g., the code name or security settings) is updated.

Update language metadata

BeforeUpdateContentItemLanguageMetadataEvent AfterUpdateContentItemLanguageMetadataEvent

Data class: UpdateContentItemLanguageMetadataEventData

Occurs when language-specific metadata (e.g., display name or publish/unpublish scheduling) is updated.

Publish

AfterPublishContentItemEvent

Data class: PublishContentItemEventData

Occurs after a content item is published. After-only event.

Unpublish

AfterUnpublishContentItemEvent

Data class: UnpublishContentItemEventData

Occurs after a content item is unpublished. After-only event.

Delete

AfterDeleteContentItemEvent

Data class: DeleteContentItemEventData

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

BeforeCreateWebPageEvent AfterCreateWebPageEvent

Data class: CreateWebPageEventData

Occurs when a page is created. ID, Guid, and TreePath are only populated in the After event.

Create language variant

BeforeCreateWebPageLanguageVariantEvent AfterCreateWebPageLanguageVariantEvent

Data class: CreateWebPageLanguageVariantEventData

Occurs when a new language variant of a page is created.

Create folder

BeforeCreateFolderEvent AfterCreateFolderEvent

Data class: CreateFolderEventData

Occurs when a folder is created in the page tree. ID, Guid, and TreePath are only populated in the After event.

Create folder language variant

BeforeCreateFolderLanguageVariantEvent AfterCreateFolderLanguageVariantEvent

Data class: CreateFolderLanguageVariantEventData

Occurs when a new language variant of a folder is created.

Update draft

BeforeUpdateWebPageDraftEvent AfterUpdateWebPageDraftEvent

Data class: UpdateWebPageDraftEventData

Occurs when a page draft is updated.

Update metadata

BeforeUpdateWebPageMetadataEvent AfterUpdateWebPageMetadataEvent

Data class: UpdateWebPageMetadataEventData

Occurs when page metadata (e.g., the code name or security settings) is updated.

Update language metadata

BeforeUpdateWebPageLanguageMetadataEvent AfterUpdateWebPageLanguageMetadataEvent

Data class: UpdateWebPageLanguageMetadataEventData

Occurs when language-specific metadata of a page (e.g., display name or publish/unpublish scheduling) is updated.

Update tree path slug

BeforeUpdateWebPageTreePathSlugEvent AfterUpdateWebPageTreePathSlugEvent

Data class: UpdateWebPageTreePathSlugEventData

Occurs when a page’s URL path slug is updated.

Update security settings

BeforeUpdateWebPageSecuritySettingsEvent AfterUpdateWebPageSecuritySettingsEvent

Data class: UpdateWebPageSecuritySettingsEventData

Occurs when a page’s security settings are updated.

Publish

AfterPublishWebPageEvent

Data class: PublishWebPageEventData

Occurs after a page is published. After-only event.

Unpublish

AfterUnpublishWebPageEvent

Data class: UnpublishWebPageEventData

Occurs after a page is unpublished. After-only event.

Move

AfterMoveWebPageEvent

Data class: MoveWebPageEventData

Occurs after a page is moved in the content tree. After-only event.

Delete

AfterDeleteWebPageEvent

Data class: DeleteWebPageEventData

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

BeforeCreateHeadlessItemEvent AfterCreateHeadlessItemEvent

Data class: CreateHeadlessItemEventData

Occurs when a headless item is created. ID and Guid are only populated in the After event.

Create language variant

BeforeCreateHeadlessItemLanguageVariantEvent AfterCreateHeadlessItemLanguageVariantEvent

Data class: CreateHeadlessItemLanguageVariantEventData

Occurs when a new language variant of a headless item is created.

Update draft

BeforeUpdateHeadlessItemDraftEvent AfterUpdateHeadlessItemDraftEvent

Data class: UpdateHeadlessItemDraftEventData

Occurs when a headless item draft is updated.

Update metadata

BeforeUpdateHeadlessItemMetadataEvent AfterUpdateHeadlessItemMetadataEvent

Data class: UpdateHeadlessItemMetadataEventData

Occurs when headless item metadata is updated (e.g., security settings).

Update language metadata

BeforeUpdateHeadlessItemLanguageMetadataEvent AfterUpdateHeadlessItemLanguageMetadataEvent

Data class: UpdateHeadlessItemLanguageMetadataEventData

Occurs when language-specific metadata of a headless item (e.g., display name) is updated.

Publish

AfterPublishHeadlessItemEvent

Data class: PublishHeadlessItemEventData

Occurs after a headless item is published. After-only event.

Unpublish

AfterUnpublishHeadlessItemEvent

Data class: UnpublishHeadlessItemEventData

Occurs after a headless item is unpublished. After-only event.

Delete

AfterDeleteHeadlessItemEvent

Data class: DeleteHeadlessItemEventData

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.

  1. Open your Xperience solution in your preferred IDE.

  2. 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);
         }
     }
     
  3. Create a module class and register the handler in the OnPreInit override:

    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.