Handle Content Events
Why handle content events
Section titled “Why handle content events”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.
What you will do
Section titled “What you will do”- Subscribe to content events in an initialization module
- Handle pre-save events to validate or modify content
- Handle post-publish events for notifications and side effects
- Clean up subscriptions on shutdown
Subscribe to events
Section titled “Subscribe to events”Hook into IContentEvents inside an initialization module so your handlers are active for the lifetime of the application.
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.
Validate content before saving
Section titled “Validate content before saving”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:
| Approach | Best for | Trade-off |
|---|---|---|
| Content events | Immediate validation, audit logging, side effects | Runs in-process — slow handlers block the editor |
| Webhooks (SaaS) | External system integration, async processing | Eventually consistent — not immediate |
| Scheduled jobs | Batch processing, periodic checks | Not 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.
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.
React to published content
Section titled “React to published content”Post-publish events run after content is live. Use them for notifications, external system sync, or analytics.
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:
- The initialization module has
[InitializableModule]and[ModuleDependency]attributes - The assembly containing the module is referenced by the web project
- You are subscribing to the correct event (e.g.,
PublishedContentnotPublishingContent)
Prevent content deletion
Section titled “Prevent content deletion”You can block deletion of protected content by cancelling the event.
private static void OnDeletingContent(
object? sender, ContentEventArgs e)
{
if (e.ContentLink == ContentReference.StartPage)
{
e.CancelAction = true;
e.CancelReason = "The start page cannot be deleted.";
}
} Log content moves
Section titled “Log content moves”Track when editors reorganize the content tree.
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}");
}
} Available events
Section titled “Available events”| Event | Timing | Use case |
|---|---|---|
CreatingContent / CreatedContent | Before/after create | Set defaults, log creation |
SavingContent / SavedContent | Before/after save | Validation, audit trail |
PublishingContent / PublishedContent | Before/after publish | Approval checks, notifications |
DeletingContent / DeletedContent | Before/after delete | Protection rules, cleanup |
MovedContent | After move | Tree reorganization logging |
1. You need to enforce a business rule that all ArticlePage instances must have a MetaDescription before they can be saved. You also want the editor to see a clear error message. Which approach is most appropriate?
A SavingContent (pre-save) event handler can inspect content before it reaches the database and block the save with CancelAction = true and a CancelReason message shown to the editor. This provides synchronous, immediate validation.
A SavingContent (pre-save) event handler can inspect content before it reaches the database and block the save with CancelAction = true and a CancelReason message shown to the editor. This provides synchronous, immediate validation.
Review this topic →2. Your post-publish event handler sends a Slack notification via an HTTP API call, which takes 2-3 seconds. Editors complain that publishing feels slow. What should you change?
Post-publish handlers block the editor until completion. Slow operations like external API calls should be offloaded to a background job or message queue so the handler returns immediately while the notification is sent asynchronously.
Post-publish handlers block the editor until completion. Slow operations like external API calls should be offloaded to a background job or message queue so the handler returns immediately while the notification is sent asynchronously.
Review this topic →3. On CMS SaaS, you need to trigger an external service whenever content is published. In-process .NET event handlers are not available. Which mechanism should you use instead?
CMS SaaS does not support in-process .NET event handlers. Instead, you configure webhook subscriptions in the Optimizely dashboard to receive HTTP callbacks when content lifecycle events occur.
CMS SaaS does not support in-process .NET event handlers. Instead, you configure webhook subscriptions in the Optimizely dashboard to receive HTTP callbacks when content lifecycle events occur.
Review this topic →