---
title: Register a Reactor at Runtime
editUrl: https://github.com/Cratis/Chronicle/edit/main/Documentation/reactors/runtime-registration.mdx
---

import { Tabs, TabItem } from '@astrojs/starlight/components';

When a bridge or plugin host discovers its event subscriptions from configuration, it need not create a reactor class for each subscription. Register an in-process reactor with a stable ID, one or more event types (including their generations), and an asynchronous handler:

<Tabs syncKey="chronicle-client">
<TabItem label="C#">

```csharp
using System.Text.Json.Nodes;
using Cratis.Chronicle.Events;
using Cratis.Chronicle.EventSequences;

public interface IOrdersBridgeForwarder
{
    Task Forward(EventType eventType, JsonObject content, IReadOnlyDictionary<int, string> generations, CancellationToken cancellationToken);
}

public class OrdersBridgeRegistration
{
    public async Task Register(IEventStore eventStore, IOrdersBridgeForwarder forwarder)
    {
        await eventStore.Reactors.Register(
            "orders-bridge",
            reactor => reactor
                .WithEventType(new EventType("OrderPlaced", 1))
                .WithEventType(new EventType("OrderPlaced", 2))
                .OnEventSequence(EventSequenceId.Log)
                .NotReplayable(),
            async (@event, cancellationToken) =>
            {
                // Content is JsonObject; generational content contains raw JSON.
                await forwarder.Forward(@event.Context.EventType, @event.Content, @event.GenerationalContent, cancellationToken);
            });
    }

    public void Unregister(IEventStore eventStore)
    {
        eventStore.Reactors.Unregister("orders-bridge");
    }
}
```

[View C# snippet source on GitHub](https://github.com/Cratis/Chronicle/blob/main/Documentation/client-snippets/reactors/runtime-registration/delegate.md)

</TabItem>
</Tabs>

The handler receives the event context and JSON content as delivered by Chronicle, along with the kernel's generation-to-JSON map. It does not deserialize the event into a typed event or select a different generation's content. The map may contain only some generations; it is not a promise that every subscribed generation is present. On storage reads and catch-up, the context's generation reflects what the kernel delivers, which may differ from the generation originally appended when content was migrated. Do not use it as an immutable original-generation identifier across replay.

Registration observes the event log by default; use `OnEventSequence` to choose another sequence. A reactor is replayable by default; use `NotReplayable` if repeating its side effects is unsafe. The handler must complete successfully before Chronicle acknowledges its partition. If it throws or its task fails, the partition fails without advancing past that event. Make downstream effects idempotent to tolerate retries and reconnects.

Keep the reactor ID stable across restarts so Chronicle can retain its observation position. The returned handler exposes state and failed partitions through the same APIs as a typed reactor; use `GetHandlerById` to look it up later. `Unregister(id)` disconnects the local observation stream and removes the callback from this client. It does not erase the kernel's stored observer state. A still-registered reactor reconnects with its original subscription and callback. Unregistering an unknown ID does nothing. Delegate handlers report `typeof(object)` as their `ReactorType`. Duplicate reactor IDs are rejected, including IDs shared by two discovered reactor classes: one kernel observer ID cannot independently track two reactors.

For known event types, prefer a [typed reactor](/chronicle/reactors/getting-started/) with discovered handler methods.
