---
title: Tag Enrichers
editUrl: https://github.com/Cratis/Chronicle/edit/main/Documentation/events/event-tag-enrichers.mdx
description: Add named tags to every appended event from one place with IEventTagEnricher and ambient tag scopes, instead of at each append call.
lastUpdated: 2026-11-04T00:00:00.000Z
---

[Named tags](/chronicle/events/appending-with-tags/) can be given at every append call, but some tags belong to the surroundings of the append rather than to the code that does it. Chronicle resolves the tags of every event in one step, used by every append path: single appends, `AppendMany`, and units of work.

## Order

For each event the tags are resolved in this order and merged without duplicates:

1. The static `[Tag]` tags of the event type.
2. The tags already on the event, including those given at the call site.
3. The built-in enrichers: the ambient scope and `[Audience]` properties.
4. Your own `IEventTagEnricher` implementations.

For a unit of work this happens when the event is added, not when the unit commits.

## Writing an enricher

Implement `IEventTagEnricher`. Enrichers are discovered automatically, resolved through dependency injection, and all of them run for every event. They must be pure, synchronous and additive: they can add tags, never remove or change them.

**C#**

```csharp
using Cratis.Chronicle;

public class EnrichersTenantRegionTagEnricher(IRegionLookup regions) : IEventTagEnricher
{
    public IEnumerable<NamedTag> GetTags(EventTagContext context)
    {
        if (context.EventSequenceId != Cratis.Chronicle.EventSequences.EventSequenceId.Outbox)
        {
            return [];
        }

        return [new NamedTag(new TagName("region"), regions.For(context.EventSourceId.Value))];
    }
}

public interface IRegionLookup
{
    string For(string eventSourceId);
}
```

[View C# snippet source on GitHub](https://github.com/Cratis/Chronicle/blob/main/Documentation/client-snippets/events/event-tag-enrichers/enricher.md)

The `EventTagContext` gives you the event store name, the event sequence, the event source id, the event type, the event itself and the tags it already has.

## Ambient scopes

`EventTags.Scope(name, value)` stamps a tag on every event appended inside the scope, without the appending code having to know. The scope flows with the async execution context and is closed by disposing it.

**C#**

```csharp
using Cratis.Chronicle;
using Cratis.Chronicle.Events;

[EventType]
public record EnrichersOrderPlaced(string OrderId);

public static class EnrichersAmbientScope
{
    public static async Task Place(IEventStore eventStore)
    {
        // Every event appended inside the scope gets region=eu, without the append code knowing.
        using var scope = EventTags.Scope("region", "eu");
        await eventStore.EventLog.Append("order-1", new EnrichersOrderPlaced("order-1"));
    }
}
```

[View C# snippet source on GitHub](https://github.com/Cratis/Chronicle/blob/main/Documentation/client-snippets/events/event-tag-enrichers/ambient-scope.md)

Nested scopes add up. `EventTags.Audience(value)` is the same with the `audience` name; see [Audiences](/chronicle/events/audiences/) for where it applies.
