---
title: Send and receive custom events
slug: cms-13/docs/send-and-receive-custom-events
createdAt: 2026-07-30T20:22:04.530Z
---

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:

```csharp
// 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:

```csharp
// 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:

```csharp
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`:

```csharp
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.

```csharp
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:

```csharp
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 += handler`                                              | `AddCmsEventSubscriber<TEventData, TSubscriber>()` at startup         |
| `OnEvent(object, EventNotificationEventArgs)`                          | `IEventSubscriber<T>.HandleAsync(T, EventContext, CancellationToken)` |
| Manual GUID management with `RaiserId` and `EventId`                   | GUID defined with `EventDataAttribute` on the type                    |
| `EventNotificationEventArgs.RaiserId` check for filtering local events | Check `EventContext.Broadcasted` in `HandleAsync` subscriber method   |
