---
title: Append through a definition
editUrl: https://github.com/Cratis/Chronicle/edit/main/Documentation/event-sources/appending.mdx
description: Append one event, several events, or events for several event sources through an event source definition, and what Chronicle rejects.
---

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


Use this page to write events through a definition. If you have not declared one yet, read [Define an event source](/chronicle/event-sources/defining/).

## Append to a stream

`Append<TSource>` takes the event source id and the event as usual. The stream name and the stream id are optional.

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

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

[EventType]
public record AccountOpened(string Owner);

[EventType]
public record MoneyDeposited(decimal Amount);

[EventType]
public record MoneyWithdrawn(decimal Amount);

[EventSource("Account", Description = "A customer's bank account", Concurrency = ConcurrencyDimensions.EventSourceId)]
[EventStream("Onboarding", Description = "Opening the account and verifying the customer")]
[EventStream(
    "Transactions",
    Description = "Deposits and withdrawals, one stream per month",
    Concurrency = ConcurrencyDimensions.EventSourceId | ConcurrencyDimensions.EventStreamType | ConcurrencyDimensions.EventStreamId)]
public class AccountEventSource : IEventSource;

public class AccountWriter(IEventLog eventLog)
{
    public Task<AppendResult> Deposit(EventSourceId accountId, decimal amount, string month) =>
        eventLog.Append<AccountEventSource>(
            accountId,
            new MoneyDeposited(amount),
            eventStream: "Transactions",
            eventStreamId: month);
}
```

[View C# snippet source on GitHub](https://github.com/Cratis/Chronicle/blob/main/Documentation/client-snippets/event-sources/appending/append-to-stream.md)

</TabItem>
</Tabs>

Chronicle fills in the routing from the definition:

| Value on the event | Taken from |
| --- | --- |
| Event source type | The definition's name, `Account` here |
| Event stream type | The stream name you pass, or the default (`All`) when you pass none |
| Event stream id | The stream id you pass, or the default when you pass none |
| `EventContext.EventSource` | The definition's name |

You can pass `eventSourceType` or `eventStreamType` on the definition-less overloads, but not on the typed ones: they have no such parameters, and the definition decides.

## Append without a stream

A stream is optional. An event appended without one carries the event source type and the default stream:

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

```csharp
using Cratis.Chronicle.EventSequences;

public class AccountOpener(IEventLog eventLog)
{
    // No stream: the event is written with the event source type "Account" and the default stream.
    public Task<AppendResult> Open(EventSourceId accountId, string owner) =>
        eventLog.Append<AccountEventSource>(accountId, new AccountOpened(owner));
}
```

[View C# snippet source on GitHub](https://github.com/Cratis/Chronicle/blob/main/Documentation/client-snippets/event-sources/appending/append-without-stream.md)

</TabItem>
</Tabs>

The concurrency guard then comes from the event source's own dimensions, because no stream supplies others.

## Append several events

`AppendMany<TSource>` writes a list of events for one event source id, to one stream and one stream id, in one atomic append.

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

```csharp
using Cratis.Chronicle.EventSequences;

public class MonthlyPosting(IEventLog eventLog)
{
    // Every event goes to the same event source id, stream and stream id.
    public Task<AppendManyResult> Post(EventSourceId accountId, string month, decimal deposit, decimal withdrawal) =>
        eventLog.AppendMany<AccountEventSource>(
            accountId,
            [new MoneyDeposited(deposit), new MoneyWithdrawn(withdrawal)],
            eventStream: "Transactions",
            eventStreamId: month);
}
```

[View C# snippet source on GitHub](https://github.com/Cratis/Chronicle/blob/main/Documentation/client-snippets/event-sources/appending/append-many.md)

</TabItem>
</Tabs>

## Append for several event sources

To write events for several event sources in one atomic append, give each `EventForEventSourceId` the definition it goes through with `EventSource`, and its stream with `EventStream` and `EventStreamId`.

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

```csharp
using Cratis.Chronicle.EventSequences;

public class Transfers(IEventLog eventLog)
{
    // Two event sources, one atomic append: both events are written, or neither is.
    public Task<AppendManyResult> Transfer(EventSourceId from, EventSourceId to, decimal amount, string month) =>
        eventLog.AppendMany(
        [
            new EventForEventSourceId(from, new MoneyWithdrawn(amount))
            {
                EventSource = typeof(AccountEventSource),
                EventStream = "Transactions",
                EventStreamId = month
            },
            new EventForEventSourceId(to, new MoneyDeposited(amount))
            {
                EventSource = typeof(AccountEventSource),
                EventStream = "Transactions",
                EventStreamId = month
            }
        ]);
}
```

[View C# snippet source on GitHub](https://github.com/Cratis/Chronicle/blob/main/Documentation/client-snippets/event-sources/appending/several-sources.md)

</TabItem>
</Tabs>

Every event has exactly one event source. The append is all or nothing: if either event fails a constraint or a concurrency check, neither is written. Events in one batch can go through different definitions, and some can skip definitions altogether.

Each event source id in the batch is guarded by one concurrency scope. When events for the same id need different guards, Chronicle refuses the batch. See [Concurrency](/chronicle/event-sources/concurrency/#a-batch-that-needs-two-guards-for-one-event-source-id).

## Other overloads

| API | Use |
| --- | --- |
| `Append(Type eventSource, …)` and `AppendMany(Type eventSource, …)` | When the definition is known only at run time |
| `AppendPrepared<TSource>` | When you prepared the event content on the same sequence |
| `EventForEventSourceId.EventSource` and `.EventStream` | Batches across event sources |

The unit of work's `Transactional` sequence has no typed overloads. To stage an event with the unit of work, pass the routing values yourself. They must equal what the definition declares.

## What Chronicle rejects

| Problem | What happens | When |
| --- | --- | --- |
| The type is not a discovered definition | `UnknownEventSource` is thrown | Before anything is sent |
| The stream is not one the definition declares | `EventStreamDoesNotBelongToEventSource` is thrown | Before anything is sent |
| An `EventForEventSourceId` carries an explicit `EventSourceType` or `EventStreamType` that contradicts the definition | `EventRoutingContradictsEventSource` is thrown | Before anything is sent |
| The kernel does not know the definition, the stream, or the event source type | The result carries an error that starts with `UnknownEventSource`, `UnknownEventStreamForEventSource` or `EventSourceTypeDoesNotMatchEventSource` | At append, for example when the definition was not registered yet |
| A constraint or a concurrency scope fails | `IsSuccess` is false and the result lists the violations | At append |

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

```csharp
using Cratis.Chronicle.EventSequences;
using Cratis.Chronicle.EventSources;

public class CarefulDeposits(IEventLog eventLog)
{
    public async Task<string> Deposit(EventSourceId accountId, decimal amount, string stream, string month)
    {
        try
        {
            var result = await eventLog.Append<AccountEventSource>(
                accountId,
                new MoneyDeposited(amount),
                eventStream: stream,
                eventStreamId: month);

            return result.IsSuccess ? "Deposited" : "Rejected by the kernel";
        }
        catch (EventStreamDoesNotBelongToEventSource)
        {
            // The stream is not one of the streams the definition declares. Nothing was sent.
            return $"'{stream}' is not a stream of the Account event source";
        }
        catch (UnknownEventSource)
        {
            // The type is not a discovered definition. Nothing was sent.
            return "AccountEventSource is not discovered";
        }
    }
}
```

[View C# snippet source on GitHub](https://github.com/Cratis/Chronicle/blob/main/Documentation/client-snippets/event-sources/appending/errors.md)

</TabItem>
</Tabs>

Read the result. A constraint violation or a concurrency violation does not throw.

## Next steps

- [Concurrency](/chronicle/event-sources/concurrency/): choose what an append conflicts with.
- [Scenarios](/chronicle/event-sources/scenarios/): complete examples.
- [Dynamic Consistency Boundary](/chronicle/event-sources/dcb/): a guarded decision that appends through a definition. The wider picture is in [Dynamic Consistency Boundary in Chronicle](/chronicle/dynamic-consistency-boundary/chronicle/).
- [Appending events](/chronicle/events/appending/)
