Skip to content

Handle Content Events

⏱ 15 minutes intermediate

Content events let you run logic automatically when editors create, update, publish, delete, or move content. Common use cases include audit logging, cache invalidation, sending notifications, and enforcing business rules before content goes live.

  1. Subscribe to content events in an initialization module
  2. Handle pre-save events to validate or modify content
  3. Handle post-publish events for notifications and side effects
  4. Clean up subscriptions on shutdown

Hook into IContentEvents inside an initialization module so your handlers are active for the lifetime of the application.

Subscribe to content events
csharp
using Optimizely.Cms.Core;
using Optimizely.Cms.Core.Events;
using Optimizely.Cms.Framework.Initialization;
using Optimizely.Cms.Core.Initialization;

namespace MySite.Events;

[InitializableModule]
[ModuleDependency(typeof(CmsCoreInitialization))]
public class ContentEventHandler : IInitializableModule
{
    public void Initialize(InitializationEngine context)
    {
        var events = context.Locate.Advanced
            .GetInstance<IContentEvents>();

        events.SavingContent += OnSavingContent;
        events.PublishedContent += OnPublishedContent;
        events.DeletingContent += OnDeletingContent;
        events.MovedContent += OnMovedContent;
    }

    public void Uninitialize(InitializationEngine context)
    {
        var events = context.Locate.Advanced
            .GetInstance<IContentEvents>();

        events.SavingContent -= OnSavingContent;
        events.PublishedContent -= OnPublishedContent;
        events.DeletingContent -= OnDeletingContent;
        events.MovedContent -= OnMovedContent;
    }
}

Always unsubscribe in Uninitialize. This prevents duplicate handlers after application pool recycling.

Verify: Run the application and check the startup log for your module name. If the module does not appear, check that [ModuleDependency] references a valid dependency and that the assembly is loaded.

Pre-save events let you inspect or reject content before it reaches the database. Set e.CancelAction = true to block the save and display a message to the editor.

When to use events vs other approaches:

ApproachBest forTrade-off
Content eventsImmediate validation, audit logging, side effectsRuns in-process — slow handlers block the editor
Webhooks (SaaS)External system integration, async processingEventually consistent — not immediate
Scheduled jobsBatch processing, periodic checksNot real-time — runs on a schedule

Choose events when you need synchronous validation or immediate side effects. Choose webhooks or scheduled jobs when the operation can be deferred.

Block save with validation
csharp
private static void OnSavingContent(
    object? sender, ContentEventArgs e)
{
    if (e.Content is ArticlePage article)
    {
        if (string.IsNullOrWhiteSpace(article.MetaDescription))
        {
            e.CancelAction = true;
            e.CancelReason = "Articles require a meta description before saving.";
        }
    }
}

The editor sees the CancelReason message as a red validation banner at the top of the editor. The content remains in draft state — nothing is saved.

If you block saves unintentionally: Remove or adjust the condition in your handler, rebuild, and restart the application. Editors cannot override a CancelAction block — your code is the only way to release it.

Post-publish events run after content is live. Use them for notifications, external system sync, or analytics.

Send notification on publish
csharp
private static void OnPublishedContent(
    object? sender, ContentEventArgs e)
{
    if (e.Content is NewsPage news)
    {
        var logger = LoggerFactory
            .Create(b => b.AddConsole())
            .CreateLogger("ContentEvents");

        logger.LogInformation(
            "Published news article: {Title} (ID: {Id})",
            news.Name,
            e.ContentLink);

        // Trigger webhook, update search index,
        // or send Slack notification here
    }
}

Post-publish handlers should be fast — the editor waits for all handlers to complete before seeing the “Published” confirmation. Move slow operations (API calls, email sends, search re-indexing) to a background job using IHostedService or a message queue.

Troubleshooting: If your event handler does not fire, check:

  1. The initialization module has [InitializableModule] and [ModuleDependency] attributes
  2. The assembly containing the module is referenced by the web project
  3. You are subscribing to the correct event (e.g., PublishedContent not PublishingContent)

You can block deletion of protected content by cancelling the event.

Block deletion of key pages
csharp
private static void OnDeletingContent(
    object? sender, ContentEventArgs e)
{
    if (e.ContentLink == ContentReference.StartPage)
    {
        e.CancelAction = true;
        e.CancelReason = "The start page cannot be deleted.";
    }
}

Track when editors reorganize the content tree.

Log content moves
csharp
private static void OnMovedContent(
    object? sender, ContentEventArgs e)
{
    if (e is MoveContentEventArgs moveArgs)
    {
        Console.WriteLine(
            $"Content {e.ContentLink} moved " +
            $"from {moveArgs.OriginalParent} " +
            $"to {moveArgs.TargetLink}");
    }
}
EventTimingUse case
CreatingContent / CreatedContentBefore/after createSet defaults, log creation
SavingContent / SavedContentBefore/after saveValidation, audit trail
PublishingContent / PublishedContentBefore/after publishApproval checks, notifications
DeletingContent / DeletedContentBefore/after deleteProtection rules, cleanup
MovedContentAfter moveTree reorganization logging