HomeDev GuideRecipesAPI Reference
Dev GuideAPI ReferenceUser GuideGitHubNuGetDev CommunityOptimizely AcademySubmit a ticketLog In
Dev Guide

Send and receive custom events

Define, publish, and subscribe to custom events in Optimizely CMS 13 using the typed event API for in-process and load-balanced distribution.

Use the typed event API to define, publish, and subscribe to custom events in Optimizely Content Management System (CMS). This API replaces the older IEventRegistry-based approach with a strongly typed model built around three core interfaces:

  • IEventData -- Marker interface for your custom event data type.
  • IEventPublisher -- Publishes events to local and remote subscribers.
  • IEventSubscriber<T> -- Subscribes to events of a specific type.

Event types

Register custom event types at startup using the AddCmsEventType<T> extension method on IServiceCollection. Each event type that supports remote distribution requires a unique GUID identifier for cross-instance routing. Provide the identifier directly through AddCmsEventType<T> or by decorating the event data class with EventDataAttribute. Configure events intended for remote distribution with the broadcast option.

Method-provided identifier

The following example registers an event type with the identifier and broadcast settings as method arguments:

// EventData definition
public class ExampleEvent : IEventData { }

// Service registration method
public void ConfigureServices(IServiceCollection services)
{
    services.AddCmsEvents();

    // Registration with identity and broadcast settings as arguments
    services.AddCmsEventType<ExampleEvent>(new Guid("b9846410-02e0-4a69-8b68-fd07d12264ec"), broadcast: true);
}

Attribute-provided metadata

The following example declares the identifier and broadcast settings on the event type itself:

// EventData definition with identity and broadcast settings declared on the type
[EventData("b9846410-02e0-4a69-8b68-fd07d12264ec", Broadcast = true)]
public class ExampleEvent : IEventData { }

// Service registration method
public void ConfigureServices(IServiceCollection services)
{
    services.AddCmsEvents();

    // Registration without arguments
    services.AddCmsEventType<ExampleEvent>();
}

Serialization requirements

Event data types intended for distribution must be JSON-serializable so the event provider can transport them between instances. Keep event data simple (primitive types, strings, and dates) and avoid circular references or complex object graphs.

Publish events

Inject IEventPublisher into your class and call PublishAsync to raise an event:

using EPiServer.Events;

[EventData("b378eacd-41ab-4313-a411-6aa33403bb1a", Broadcast = true)]
public record CustomSaveEvent(Guid Id) : IEventData { }

public class CustomRepository(IEventPublisher eventPublisher)
{
    public async Task SaveAsync(Custom instance, CancellationToken cancellationToken = default)
    {
        // Save the custom instance here...

        // Clear local cache
        await ClearCacheAsync(instance.Id, cancellationToken);

        // Publish the event (broadcast behavior is determined by the attribute)
        await eventPublisher.PublishAsync(new CustomSaveEvent(instance.Id), cancellationToken);
    }

    public async Task ClearCacheAsync(Guid id, CancellationToken cancellationToken = default) { }
}

Override broadcast at publish time

If the event type does not have Broadcast = true set on its attribute, override the broadcast behavior per call using EventPublishingOptions:

await _eventPublisher.PublishAsync(
    new CustomSaveEvent(cacheKey),
    new EventPublishingOptions { Broadcast = true }
);

When Broadcast is set to true on the attribute, passing EventPublishingOptions is not required.

Subscribe to events

Create a class that implements IEventSubscriber<T> and register it in the service container. When the event system receives an event, it calls HandleAsync on each matching subscriber.

public class CustomSaveEventSubscriber(CustomRepository repository) : IEventSubscriber<CustomSaveEvent> 
{ 
    public async Task HandleAsync(CustomSaveEvent eventData, EventContext context, CancellationToken cancellationToken = default)
    {
        // Only respond to broadcasted events because local saves are already handled
        if (context.Broadcasted)
        {
            await repository.ClearLocalCacheAsync(eventData.Id);
        }
    }
}

public void ConfigureServices(IServiceCollection services)
{
    services.AddCmsEvents();
    services.AddCmsEventType<ExampleEvent>();

    // Register subscriber
    services.AddCmsEventSubscriber<ExampleEvent, ExampleEventSubscriber>();
}

Subscribe to multiple event types

Subscribe to multiple event types using one IEventSubscriber by subscribing to a base event type:

public record CustomEvent : IEventData 
{ 
    public required Guid Id { get; init; }
}
public record CustomSaveEvent : CustomEvent { }
public record CustomDeleteEvent : CustomEvent { }

public class CustomSaveEventSubscriber(CustomRepository repository) : IEventSubscriber<CustomEvent> 
{ 
    public async Task HandleAsync(CustomEvent eventData, EventContext context, CancellationToken cancellationToken = default)
    {
        if (context.Broadcasted)
        {
            await repository.ClearLocalCacheAsync(eventData.Id);

            // Additional task for a specific event type
            if (eventData is CustomDeleteEvent) 
            {
                await repository.RebuildIndex();
            }
        }
    }
}

Thread safety and event ordering

The event system does not guarantee sequential processing. Events from remote instances can arrive in any order, and the system can process them concurrently. Ensure your event handlers are thread-safe and do not depend on a specific arrival order.

Migration from IEventRegistry

If you are upgrading from the older IEventRegistry API, the following table summarizes the key changes:

Legacy API (IEventRegistry)New API
[EventsServiceKnownType]Register type with AddCmsEventType<T>() at startup
IEventRegistry.Get(eventGuid)new MyEventData()
event.Raise(raiserId, data)IEventPublisher.PublishAsync(eventData)
event.Raised += handlerAddCmsEventSubscriber<TEventData, TSubscriber>() at startup
OnEvent(object, EventNotificationEventArgs)IEventSubscriber<T>.HandleAsync(T, EventContext, CancellationToken)
Manual GUID management with RaiserId and EventIdGUID defined with EventDataAttribute on the type
EventNotificationEventArgs.RaiserId check for filtering local eventsCheck EventContext.Broadcasted in HandleAsync subscriber method