---
title: Removing Read Models
editUrl: https://github.com/Cratis/Chronicle/edit/main/Documentation/projections/model-bound/removal.mdx
description: Model-bound projections remove root read models with RemovedWith and child items with RemovedWith or RemovedWithJoin. Root-level RemovedWithJoin is not…
---

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

Model-bound projections remove root read models with `RemovedWith` and child items with `RemovedWith` or `RemovedWithJoin`. Root-level `RemovedWithJoin` is not yet supported: the engine ignores it and logs a warning. Do not rely on it to remove root instances.

## Removing Root Read Models

Use `RemovedWith` at the class level to specify which event removes the entire read model instance:

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

```csharp
using Cratis.Chronicle.Events;
using Cratis.Chronicle.Keys;
using Cratis.Chronicle.Projections.ModelBound;

[EventType]
public record MbRemovalAccountOpened(string Name, decimal Balance);

[EventType]
public record MbRemovalAccountClosed;

[RemovedWith<MbRemovalAccountClosed>]
public record MbRemovalAccount(
    [Key]
    Guid Id,

    [SetFrom<MbRemovalAccountOpened>(nameof(MbRemovalAccountOpened.Name))]
    string Name,

    [SetFrom<MbRemovalAccountOpened>(nameof(MbRemovalAccountOpened.Balance))]
    decimal Balance);
```

[View C# snippet source on GitHub](https://github.com/Cratis/Chronicle/blob/main/Documentation/client-snippets/projections/model-bound/removal/basic.md)

</TabItem>
<TabItem label="Kotlin">

```kotlin
import io.cratis.chronicle.events.EventType
import io.cratis.chronicle.projections.FromEvent
import io.cratis.chronicle.projections.RemovedWith
import io.cratis.chronicle.projections.SetFrom
import io.cratis.chronicle.readModels.ReadModel

@EventType
data class MbRemovalAccountOpened(val name: String, val balance: Double)

@EventType
class MbRemovalAccountClosed

@ReadModel
@FromEvent(MbRemovalAccountOpened::class)
@RemovedWith(MbRemovalAccountClosed::class)
data class MbRemovalAccount(
    @SetFrom("name", MbRemovalAccountOpened::class)
    val name: String = "",

    @SetFrom("balance", MbRemovalAccountOpened::class)
    val balance: Double = 0.0
)
```

[View Kotlin snippet source on GitHub](https://github.com/Cratis/Chronicle.Kotlin/blob/main/Documentation/client-snippets/projections/model-bound/removal/basic.md)

</TabItem>
<TabItem label="Java">

```java
import io.cratis.chronicle.events.EventType;
import io.cratis.chronicle.projections.FromEvent;
import io.cratis.chronicle.projections.RemovedWith;
import io.cratis.chronicle.projections.SetFrom;
import io.cratis.chronicle.readModels.ReadModel;

@EventType
record MbRemovalAccountOpened(String name, double balance) {}

@EventType
class MbRemovalAccountClosed {}

@ReadModel
@FromEvent(eventType = MbRemovalAccountOpened.class)
@RemovedWith(eventType = MbRemovalAccountClosed.class)
class MbRemovalAccount {
    @SetFrom(propertyPath = "name", eventType = MbRemovalAccountOpened.class)
    public String name = "";

    @SetFrom(propertyPath = "balance", eventType = MbRemovalAccountOpened.class)
    public double balance = 0.0;
}
```

[View Java snippet source on GitHub](https://github.com/Cratis/Chronicle.Kotlin/blob/main/Documentation/client-snippets-java/projections/model-bound/removal/basic.md)

</TabItem>
<TabItem label="Elixir">

```elixir
defmodule MyApp.Events.RemovalAccountOpened do
  use Chronicle.Events.EventType, id: "removal-account-opened-v1"

  defstruct [:name, :balance]
end

defmodule MyApp.Events.RemovalAccountClosed do
  use Chronicle.Events.EventType, id: "removal-account-closed-v1"

  defstruct []
end

defmodule MyApp.ReadModels.RemovalAccount do
  use Chronicle.ReadModels.ReadModel

  alias MyApp.Events.{RemovalAccountOpened, RemovalAccountClosed}

  defstruct [:id, :name, :balance]

  from RemovalAccountOpened,
    set: [id: :event_source_id, name: :name, balance: :balance]

  removed_with RemovalAccountClosed, []
end
```

[View Elixir snippet source on GitHub](https://github.com/Cratis/Chronicle.Elixir/blob/main/Documentation/client-snippets/projections/model-bound/removal/basic.md)

</TabItem>
<TabItem label="TypeScript">

```typescript
import { eventType, Guid, removedWith, setFrom } from '@cratis/chronicle';

@eventType()
export class MbRemovalAccountOpened {
    name = '';
    balance = 0;
}

@eventType()
export class MbRemovalAccountClosed {
}

@removedWith(MbRemovalAccountClosed)
export class MbRemovalAccount {
    id: Guid = Guid.empty;

    @setFrom(MbRemovalAccountOpened, 'name')
    name = '';

    @setFrom(MbRemovalAccountOpened, 'balance')
    balance = 0;
}
```

[View TypeScript snippet source on GitHub](https://github.com/Cratis/Chronicle.TypeScript/blob/main/Documentation/client-snippets/projections/model-bound/removal/basic.md)

</TabItem>
</Tabs>

When an `AccountClosed` event occurs, the corresponding `Account` read model is removed from the store.

### With Custom Key

You can specify which property on the event identifies the read model to remove:

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

```csharp
[EventType]
public record MbRemovalWithKeyAccountOpened(string Name);

[EventType]
public record MbRemovalWithKeyAccountClosed(Guid AccountId);

[RemovedWith<MbRemovalWithKeyAccountClosed>(key: nameof(MbRemovalWithKeyAccountClosed.AccountId))]
public record MbRemovalWithKeyAccount(
    [Key]
    Guid Id,

    [SetFrom<MbRemovalWithKeyAccountOpened>(nameof(MbRemovalWithKeyAccountOpened.Name))]
    string Name);
```

[View C# snippet source on GitHub](https://github.com/Cratis/Chronicle/blob/main/Documentation/client-snippets/projections/model-bound/removal/with-key.md)

</TabItem>
<TabItem label="Kotlin">

```kotlin
import io.cratis.chronicle.events.EventType
import io.cratis.chronicle.projections.FromEvent
import io.cratis.chronicle.projections.RemovedWith
import io.cratis.chronicle.projections.SetFrom
import io.cratis.chronicle.readModels.ReadModel

@EventType
data class MbRemovalWithKeyAccountOpened(val name: String)

@EventType
data class MbRemovalWithKeyAccountClosed(val accountId: String)

@ReadModel
@FromEvent(MbRemovalWithKeyAccountOpened::class)
@RemovedWith(MbRemovalWithKeyAccountClosed::class, key = "accountId")
data class MbRemovalWithKeyAccount(
    @SetFrom("name", MbRemovalWithKeyAccountOpened::class)
    val name: String = ""
)
```

[View Kotlin snippet source on GitHub](https://github.com/Cratis/Chronicle.Kotlin/blob/main/Documentation/client-snippets/projections/model-bound/removal/with-key.md)

</TabItem>
<TabItem label="Java">

```java
import io.cratis.chronicle.events.EventType;
import io.cratis.chronicle.projections.FromEvent;
import io.cratis.chronicle.projections.RemovedWith;
import io.cratis.chronicle.projections.SetFrom;
import io.cratis.chronicle.readModels.ReadModel;

@EventType
record MbRemovalWithKeyAccountOpened(String name) {}

@EventType
record MbRemovalWithKeyAccountClosed(String accountId) {}

@ReadModel
@FromEvent(eventType = MbRemovalWithKeyAccountOpened.class)
@RemovedWith(eventType = MbRemovalWithKeyAccountClosed.class, key = "accountId")
class MbRemovalWithKeyAccount {
    @SetFrom(propertyPath = "name", eventType = MbRemovalWithKeyAccountOpened.class)
    public String name = "";
}
```

[View Java snippet source on GitHub](https://github.com/Cratis/Chronicle.Kotlin/blob/main/Documentation/client-snippets-java/projections/model-bound/removal/with-key.md)

</TabItem>
<TabItem label="Elixir">

```elixir
defmodule MyApp.Events.RemovalWithKeyAccountOpened do
  use Chronicle.Events.EventType, id: "removal-with-key-account-opened-v1"

  defstruct [:name]
end

defmodule MyApp.Events.RemovalWithKeyAccountClosed do
  use Chronicle.Events.EventType, id: "removal-with-key-account-closed-v1"

  defstruct [:account_id]
end

defmodule MyApp.ReadModels.RemovalWithKeyAccount do
  use Chronicle.ReadModels.ReadModel

  alias MyApp.Events.{RemovalWithKeyAccountOpened, RemovalWithKeyAccountClosed}

  defstruct [:id, :name]

  from RemovalWithKeyAccountOpened,
    set: [id: :event_source_id, name: :name]

  removed_with RemovalWithKeyAccountClosed, key: :account_id
end
```

[View Elixir snippet source on GitHub](https://github.com/Cratis/Chronicle.Elixir/blob/main/Documentation/client-snippets/projections/model-bound/removal/with-key.md)

</TabItem>
<TabItem label="TypeScript">

```typescript
import { eventType, Guid, removedWith, setFrom } from '@cratis/chronicle';

@eventType()
export class MbRemovalWithKeyAccountOpened {
    name = '';
}

@eventType()
export class MbRemovalWithKeyAccountClosed {
    accountId: Guid = Guid.empty;
}

@removedWith(MbRemovalWithKeyAccountClosed, 'accountId')
export class MbRemovalWithKeyAccount {
    id: Guid = Guid.empty;

    @setFrom(MbRemovalWithKeyAccountOpened, 'name')
    name = '';
}
```

[View TypeScript snippet source on GitHub](https://github.com/Cratis/Chronicle.TypeScript/blob/main/Documentation/client-snippets/projections/model-bound/removal/with-key.md)

</TabItem>
</Tabs>

## Multiple Removal Options

A read model can be removed by multiple different events:

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

```csharp
[EventType]
public record MbRemovalMultipleAccountOpened(string Name);

[EventType]
public record MbRemovalMultipleAccountClosed;

[EventType]
public record MbRemovalMultipleAccountMerged(Guid SourceAccountId);

[RemovedWith<MbRemovalMultipleAccountClosed>]
[RemovedWith<MbRemovalMultipleAccountMerged>(key: nameof(MbRemovalMultipleAccountMerged.SourceAccountId))]
public record MbRemovalMultipleAccount(
    [Key]
    Guid Id,

    [SetFrom<MbRemovalMultipleAccountOpened>(nameof(MbRemovalMultipleAccountOpened.Name))]
    string Name);
```

[View C# snippet source on GitHub](https://github.com/Cratis/Chronicle/blob/main/Documentation/client-snippets/projections/model-bound/removal/multiple-triggers.md)

</TabItem>
<TabItem label="Kotlin">

```kotlin
import io.cratis.chronicle.events.EventType
import io.cratis.chronicle.projections.FromEvent
import io.cratis.chronicle.projections.RemovedWith
import io.cratis.chronicle.projections.RemovedWithJoin
import io.cratis.chronicle.projections.SetFrom
import io.cratis.chronicle.readModels.ReadModel

@EventType
data class MbRemovalMultipleAccountOpened(val name: String)

@EventType
class MbRemovalMultipleAccountClosed

@EventType
data class MbRemovalMultipleAccountMerged(val sourceAccountId: String)

@EventType
class MbRemovalMultipleOrganizationClosed

@ReadModel
@FromEvent(MbRemovalMultipleAccountOpened::class)
@RemovedWith(MbRemovalMultipleAccountClosed::class)
@RemovedWith(MbRemovalMultipleAccountMerged::class, key = "sourceAccountId")
@RemovedWithJoin(MbRemovalMultipleOrganizationClosed::class)
data class MbRemovalMultipleAccount(
    @SetFrom("name", MbRemovalMultipleAccountOpened::class)
    val name: String = ""
)
```

[View Kotlin snippet source on GitHub](https://github.com/Cratis/Chronicle.Kotlin/blob/main/Documentation/client-snippets/projections/model-bound/removal/multiple-triggers.md)

</TabItem>
<TabItem label="Java">

```java
import io.cratis.chronicle.events.EventType;
import io.cratis.chronicle.projections.FromEvent;
import io.cratis.chronicle.projections.RemovedWith;
import io.cratis.chronicle.projections.RemovedWithJoin;
import io.cratis.chronicle.projections.SetFrom;
import io.cratis.chronicle.readModels.ReadModel;

@EventType
record MbRemovalMultipleAccountOpened(String name) {}

@EventType
class MbRemovalMultipleAccountClosed {}

@EventType
record MbRemovalMultipleAccountMerged(String sourceAccountId) {}

@EventType
class MbRemovalMultipleOrganizationClosed {}

@ReadModel
@FromEvent(eventType = MbRemovalMultipleAccountOpened.class)
@RemovedWith(eventType = MbRemovalMultipleAccountClosed.class)
@RemovedWith(eventType = MbRemovalMultipleAccountMerged.class, key = "sourceAccountId")
@RemovedWithJoin(eventType = MbRemovalMultipleOrganizationClosed.class)
class MbRemovalMultipleAccount {
    @SetFrom(propertyPath = "name", eventType = MbRemovalMultipleAccountOpened.class)
    public String name = "";
}
```

[View Java snippet source on GitHub](https://github.com/Cratis/Chronicle.Kotlin/blob/main/Documentation/client-snippets-java/projections/model-bound/removal/multiple-triggers.md)

</TabItem>
<TabItem label="Elixir">

```text
Elixir does not support this workflow yet.
```

[View Elixir snippet source on GitHub](https://github.com/Cratis/Chronicle.Elixir/blob/main/Documentation/client-snippets/projections/model-bound/removal/multiple-triggers.md)

</TabItem>
<TabItem label="TypeScript">

```typescript
import { eventType, Guid, removedWith, removedWithJoin, setFrom } from '@cratis/chronicle';

@eventType()
export class MbRemovalMultipleAccountOpened {
    name = '';
}

@eventType()
export class MbRemovalMultipleAccountClosed {
}

@eventType()
export class MbRemovalMultipleAccountMerged {
    sourceAccountId: Guid = Guid.empty;
}

@eventType()
export class MbRemovalMultipleOrganizationClosed {
}

@removedWith(MbRemovalMultipleAccountClosed)
@removedWith(MbRemovalMultipleAccountMerged, 'sourceAccountId')
@removedWithJoin(MbRemovalMultipleOrganizationClosed)
export class MbRemovalMultipleAccount {
    id: Guid = Guid.empty;

    @setFrom(MbRemovalMultipleAccountOpened, 'name')
    name = '';
}
```

[View TypeScript snippet source on GitHub](https://github.com/Cratis/Chronicle.TypeScript/blob/main/Documentation/client-snippets/projections/model-bound/removal/multiple-triggers.md)

</TabItem>
</Tabs>

In this example, an account can be removed by an `AccountClosed` event or an `AccountMerged` event when it is the source account.

## RemovedWithJoin

`RemovedWithJoin` on a root class is **not yet supported**. The engine logs a warning and does not subscribe to the removal event. Do not add this attribute to a root read model: it will not delete its instances. Use `RemovedWith` for root deletion, or use `RemovedWithJoin` on a child collection for child-item removal.

## Removing Children

Children can be removed in two ways:

### Property-Level Removal

Apply `RemovedWith` on the collection property alongside `ChildrenFrom`:

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

```csharp
[EventType]
public record MbChildrenRemovalPropertyLineItemAdded(Guid ItemId, string Description);

[EventType]
public record MbChildrenRemovalPropertyLineItemRemoved(Guid ItemId);

public record MbChildrenRemovalPropertyOrder(
    [Key] Guid Id,

    [ChildrenFrom<MbChildrenRemovalPropertyLineItemAdded>(key: nameof(MbChildrenRemovalPropertyLineItemAdded.ItemId))]
    [RemovedWith<MbChildrenRemovalPropertyLineItemRemoved>(key: nameof(MbChildrenRemovalPropertyLineItemRemoved.ItemId))]
    IEnumerable<MbChildrenRemovalPropertyOrderLine> Lines);

public record MbChildrenRemovalPropertyOrderLine(
    [Key] Guid Id,
    string Description);
```

[View C# snippet source on GitHub](https://github.com/Cratis/Chronicle/blob/main/Documentation/client-snippets/projections/model-bound/children/removal-property-level.md)

</TabItem>
<TabItem label="Kotlin">

```kotlin
import io.cratis.chronicle.events.EventType
import io.cratis.chronicle.projections.ChildrenFrom
import io.cratis.chronicle.projections.FromEvent
import io.cratis.chronicle.projections.RemovedWith
import io.cratis.chronicle.projections.SetFrom
import io.cratis.chronicle.readModels.ReadModel

@EventType
data class MbChildrenRemovalPropertyLineItemAdded(val itemId: String, val description: String)

@EventType
data class MbChildrenRemovalPropertyLineItemRemoved(val itemId: String)

@ReadModel
@FromEvent(MbChildrenRemovalPropertyLineItemAdded::class)
data class MbChildrenRemovalPropertyOrder(
    @ChildrenFrom(MbChildrenRemovalPropertyLineItemAdded::class, key = "itemId", identifiedBy = "itemId")
    @RemovedWith(MbChildrenRemovalPropertyLineItemRemoved::class, key = "itemId")
    val lines: List<MbChildrenRemovalPropertyOrderLine> = emptyList()
)

data class MbChildrenRemovalPropertyOrderLine(
    val itemId: String = "",

    @SetFrom("description", MbChildrenRemovalPropertyLineItemAdded::class)
    val description: String = ""
)
```

[View Kotlin snippet source on GitHub](https://github.com/Cratis/Chronicle.Kotlin/blob/main/Documentation/client-snippets/projections/model-bound/children/removal-property-level.md)

</TabItem>
<TabItem label="Java">

```java
import io.cratis.chronicle.events.EventType;
import io.cratis.chronicle.projections.ChildrenFrom;
import io.cratis.chronicle.projections.FromEvent;
import io.cratis.chronicle.projections.RemovedWith;
import io.cratis.chronicle.projections.SetFrom;
import io.cratis.chronicle.readModels.ReadModel;

import java.util.Collections;
import java.util.List;

@EventType
record MbChildrenRemovalPropertyLineItemAdded(String itemId, String description) {}

@EventType
record MbChildrenRemovalPropertyLineItemRemoved(String itemId) {}

@ReadModel
@FromEvent(eventType = MbChildrenRemovalPropertyLineItemAdded.class)
class MbChildrenRemovalPropertyOrder {
    @ChildrenFrom(eventType = MbChildrenRemovalPropertyLineItemAdded.class, key = "itemId", identifiedBy = "itemId")
    @RemovedWith(eventType = MbChildrenRemovalPropertyLineItemRemoved.class, key = "itemId")
    public List<MbChildrenRemovalPropertyOrderLine> lines = Collections.emptyList();
}

class MbChildrenRemovalPropertyOrderLine {
    public String itemId = "";

    @SetFrom(propertyPath = "description", eventType = MbChildrenRemovalPropertyLineItemAdded.class)
    public String description = "";
}
```

[View Java snippet source on GitHub](https://github.com/Cratis/Chronicle.Kotlin/blob/main/Documentation/client-snippets-java/projections/model-bound/children/removal-property-level.md)

</TabItem>
<TabItem label="Elixir">

```text
Elixir does not support this workflow yet.
```

[View Elixir snippet source on GitHub](https://github.com/Cratis/Chronicle.Elixir/blob/main/Documentation/client-snippets/projections/model-bound/children/removal-property-level.md)

</TabItem>
<TabItem label="TypeScript">

```typescript
import { childrenFrom, eventType, Guid, removedWith } from '@cratis/chronicle';
import { field } from '@cratis/fundamentals';

@eventType()
export class MbChildrenRemovalPropertyLineItemAdded {
    itemId: Guid = Guid.empty;
    description = '';
}

@eventType()
export class MbChildrenRemovalPropertyLineItemRemoved {
    itemId: Guid = Guid.empty;
}

export class MbChildrenRemovalPropertyOrderLine {
    id: Guid = Guid.empty;
    description = '';
}

export class MbChildrenRemovalPropertyOrder {
    id: Guid = Guid.empty;

    @childrenFrom(MbChildrenRemovalPropertyLineItemAdded, 'itemId')
    @removedWith(MbChildrenRemovalPropertyLineItemRemoved, 'itemId')
    @field(Array, { genericArguments: [MbChildrenRemovalPropertyOrderLine] }) lines: MbChildrenRemovalPropertyOrderLine[] = [];
}
```

[View TypeScript snippet source on GitHub](https://github.com/Cratis/Chronicle.TypeScript/blob/main/Documentation/client-snippets/projections/model-bound/children/removal-property-level.md)

</TabItem>
</Tabs>

### Class-Level Removal on Child Types

Apply `RemovedWith` directly on the child type. This is particularly useful when the same child model is used in multiple parents or when you want to keep removal logic with the child definition:

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

```csharp
[EventType]
public record MbChildrenRemovalClassLineItemAdded(Guid ItemId, string Description);

[EventType]
public record MbChildrenRemovalClassLineItemRemoved(Guid OrderId, Guid ItemId);

public record MbChildrenRemovalClassOrder(
    [Key] Guid Id,

    [ChildrenFrom<MbChildrenRemovalClassLineItemAdded>(key: nameof(MbChildrenRemovalClassLineItemAdded.ItemId))]
    IEnumerable<MbChildrenRemovalClassOrderLine> Lines);

[RemovedWith<MbChildrenRemovalClassLineItemRemoved>(
    key: nameof(MbChildrenRemovalClassLineItemRemoved.ItemId),
    parentKey: nameof(MbChildrenRemovalClassLineItemRemoved.OrderId))]
public record MbChildrenRemovalClassOrderLine(
    [Key] Guid Id,
    string Description);
```

[View C# snippet source on GitHub](https://github.com/Cratis/Chronicle/blob/main/Documentation/client-snippets/projections/model-bound/children/removal-class-level.md)

</TabItem>
<TabItem label="Kotlin">

```text
Kotlin does not support this workflow yet.
```

[View Kotlin snippet source on GitHub](https://github.com/Cratis/Chronicle.Kotlin/blob/main/Documentation/client-snippets/projections/model-bound/children/removal-class-level.md)

</TabItem>
<TabItem label="Java">

```text
Java does not support this workflow yet.
```

[View Java snippet source on GitHub](https://github.com/Cratis/Chronicle.Kotlin/blob/main/Documentation/client-snippets-java/projections/model-bound/children/removal-class-level.md)

</TabItem>
<TabItem label="Elixir">

```text
Elixir does not support this workflow yet.
```

[View Elixir snippet source on GitHub](https://github.com/Cratis/Chronicle.Elixir/blob/main/Documentation/client-snippets/projections/model-bound/children/removal-class-level.md)

</TabItem>
<TabItem label="TypeScript">

```typescript
import { childrenFrom, eventType, Guid, removedWith } from '@cratis/chronicle';
import { field } from '@cratis/fundamentals';

@eventType()
export class MbChildrenRemovalClassLineItemAdded {
    itemId: Guid = Guid.empty;
    description = '';
}

@eventType()
export class MbChildrenRemovalClassLineItemRemoved {
    orderId: Guid = Guid.empty;
    itemId: Guid = Guid.empty;
}

@removedWith(MbChildrenRemovalClassLineItemRemoved, 'itemId', 'orderId')
export class MbChildrenRemovalClassOrderLine {
    id: Guid = Guid.empty;
    description = '';
}

export class MbChildrenRemovalClassOrder {
    id: Guid = Guid.empty;

    @childrenFrom(MbChildrenRemovalClassLineItemAdded, 'itemId')
    @field(Array, { genericArguments: [MbChildrenRemovalClassOrderLine] }) lines: MbChildrenRemovalClassOrderLine[] = [];
}
```

[View TypeScript snippet source on GitHub](https://github.com/Cratis/Chronicle.TypeScript/blob/main/Documentation/client-snippets/projections/model-bound/children/removal-class-level.md)

</TabItem>
</Tabs>

Both approaches produce the same result. The class-level approach keeps the removal definition with the child type, while the property-level approach keeps it with the parent.

### Parameters

For child removal, you can specify:

- **key**: Property on the event that identifies which child to remove
- **parentKey**: Property on the event that identifies the parent (defaults to EventSourceId)

## Children with RemovedWithJoin

For children that should be removed based on join events, apply `RemovedWithJoin` on the collection property (it also works at the child type's class level, the same way `RemovedWith` does above):

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

```csharp
[EventType]
public record MbChildrenRemovedFeatureActivated(Guid FeatureId, string Name);

[EventType]
public record MbChildrenRemovedFeatureDeactivated(Guid FeatureId);

public record MbChildrenRemovedSubscription(
    [Key]
    Guid SubscriptionId,

    [ChildrenFrom<MbChildrenRemovedFeatureActivated>(key: nameof(MbChildrenRemovedFeatureActivated.FeatureId))]
    [RemovedWithJoin<MbChildrenRemovedFeatureDeactivated>(key: nameof(MbChildrenRemovedFeatureDeactivated.FeatureId))]
    IEnumerable<MbChildrenRemovedFeature> Features);

public record MbChildrenRemovedFeature(
    [Key] Guid FeatureId,
    string Name);
```

[View C# snippet source on GitHub](https://github.com/Cratis/Chronicle/blob/main/Documentation/client-snippets/projections/model-bound/children/removed-with-join.md)

</TabItem>
<TabItem label="Kotlin">

```kotlin
import io.cratis.chronicle.events.EventType
import io.cratis.chronicle.projections.ChildrenFrom
import io.cratis.chronicle.projections.FromEvent
import io.cratis.chronicle.projections.RemovedWithJoin
import io.cratis.chronicle.projections.SetFrom
import io.cratis.chronicle.readModels.ReadModel

@EventType
data class MbChildrenRemovedFeatureActivated(val featureId: String, val name: String)

@EventType
data class MbChildrenRemovedFeatureDeactivated(val featureId: String)

@ReadModel
@FromEvent(MbChildrenRemovedFeatureActivated::class)
data class MbChildrenRemovedSubscription(
    @ChildrenFrom(MbChildrenRemovedFeatureActivated::class, key = "featureId", identifiedBy = "featureId")
    @RemovedWithJoin(MbChildrenRemovedFeatureDeactivated::class, key = "featureId")
    val features: List<MbChildrenRemovedFeature> = emptyList()
)

data class MbChildrenRemovedFeature(
    val featureId: String = "",

    @SetFrom("name", MbChildrenRemovedFeatureActivated::class)
    val name: String = ""
)
```

[View Kotlin snippet source on GitHub](https://github.com/Cratis/Chronicle.Kotlin/blob/main/Documentation/client-snippets/projections/model-bound/children/removed-with-join.md)

</TabItem>
<TabItem label="Java">

```java
import io.cratis.chronicle.events.EventType;
import io.cratis.chronicle.projections.ChildrenFrom;
import io.cratis.chronicle.projections.FromEvent;
import io.cratis.chronicle.projections.RemovedWithJoin;
import io.cratis.chronicle.projections.SetFrom;
import io.cratis.chronicle.readModels.ReadModel;

import java.util.Collections;
import java.util.List;

@EventType
record MbChildrenRemovedFeatureActivated(String featureId, String name) {}

@EventType
record MbChildrenRemovedFeatureDeactivated(String featureId) {}

@ReadModel
@FromEvent(eventType = MbChildrenRemovedFeatureActivated.class)
class MbChildrenRemovedSubscription {
    @ChildrenFrom(eventType = MbChildrenRemovedFeatureActivated.class, key = "featureId", identifiedBy = "featureId")
    @RemovedWithJoin(eventType = MbChildrenRemovedFeatureDeactivated.class, key = "featureId")
    public List<MbChildrenRemovedFeature> features = Collections.emptyList();
}

class MbChildrenRemovedFeature {
    public String featureId = "";

    @SetFrom(propertyPath = "name", eventType = MbChildrenRemovedFeatureActivated.class)
    public String name = "";
}
```

[View Java snippet source on GitHub](https://github.com/Cratis/Chronicle.Kotlin/blob/main/Documentation/client-snippets-java/projections/model-bound/children/removed-with-join.md)

</TabItem>
<TabItem label="Elixir">

```text
Elixir does not support this workflow yet.
```

[View Elixir snippet source on GitHub](https://github.com/Cratis/Chronicle.Elixir/blob/main/Documentation/client-snippets/projections/model-bound/children/removed-with-join.md)

</TabItem>
<TabItem label="TypeScript">

```typescript
import { childrenFrom, eventType, Guid, removedWithJoin } from '@cratis/chronicle';
import { field } from '@cratis/fundamentals';

@eventType()
export class MbChildrenRemovedFeatureActivated {
    featureId: Guid = Guid.empty;
    name = '';
}

@eventType()
export class MbChildrenRemovedFeatureDeactivated {
    featureId: Guid = Guid.empty;
}

export class MbChildrenRemovedFeature {
    featureId: Guid = Guid.empty;
    name = '';
}

export class MbChildrenRemovedSubscription {
    id: Guid = Guid.empty;

    @childrenFrom(MbChildrenRemovedFeatureActivated, 'featureId', 'featureId')
    @removedWithJoin(MbChildrenRemovedFeatureDeactivated, 'featureId')
    @field(Array, { genericArguments: [MbChildrenRemovedFeature] }) features: MbChildrenRemovedFeature[] = [];
}
```

[View TypeScript snippet source on GitHub](https://github.com/Cratis/Chronicle.TypeScript/blob/main/Documentation/client-snippets/projections/model-bound/children/removed-with-join.md)

</TabItem>
</Tabs>

## Complete Example

This example shows supported root `RemovedWith` and child `RemovedWith` declarations:

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

```csharp
// Events
[EventType]
public record MbRemovalFullShoppingCartCreated(string CustomerName);

[EventType]
public record MbRemovalFullItemAddedToCart(Guid ItemId, string ProductName, decimal Price);

[EventType]
public record MbRemovalFullItemRemovedFromCart(Guid CartId, Guid ItemId);

[EventType]
public record MbRemovalFullCartCheckedOut;

[EventType]
public record MbRemovalFullCartAbandoned;

// Read Models
[RemovedWith<MbRemovalFullCartCheckedOut>]
[RemovedWith<MbRemovalFullCartAbandoned>]
public record MbRemovalFullShoppingCart(
    [Key]
    Guid Id,

    [SetFrom<MbRemovalFullShoppingCartCreated>(nameof(MbRemovalFullShoppingCartCreated.CustomerName))]
    string Customer,

    [ChildrenFrom<MbRemovalFullItemAddedToCart>(key: nameof(MbRemovalFullItemAddedToCart.ItemId))]
    IEnumerable<MbRemovalFullCartItem> Items);

[RemovedWith<MbRemovalFullItemRemovedFromCart>(
    key: nameof(MbRemovalFullItemRemovedFromCart.ItemId),
    parentKey: nameof(MbRemovalFullItemRemovedFromCart.CartId))]
public record MbRemovalFullCartItem(
    [Key] Guid Id,

    [SetFrom<MbRemovalFullItemAddedToCart>(nameof(MbRemovalFullItemAddedToCart.ProductName))]
    string Product,

    [SetFrom<MbRemovalFullItemAddedToCart>(nameof(MbRemovalFullItemAddedToCart.Price))]
    decimal Price);
```

[View C# snippet source on GitHub](https://github.com/Cratis/Chronicle/blob/main/Documentation/client-snippets/projections/model-bound/removal/full-example.md)

</TabItem>
<TabItem label="Kotlin">

```text
Kotlin does not support this workflow yet.
```

[View Kotlin snippet source on GitHub](https://github.com/Cratis/Chronicle.Kotlin/blob/main/Documentation/client-snippets/projections/model-bound/removal/full-example.md)

</TabItem>
<TabItem label="Java">

```text
Java does not support this workflow yet.
```

[View Java snippet source on GitHub](https://github.com/Cratis/Chronicle.Kotlin/blob/main/Documentation/client-snippets-java/projections/model-bound/removal/full-example.md)

</TabItem>
<TabItem label="Elixir">

```text
Elixir does not support this workflow yet.
```

[View Elixir snippet source on GitHub](https://github.com/Cratis/Chronicle.Elixir/blob/main/Documentation/client-snippets/projections/model-bound/removal/full-example.md)

</TabItem>
<TabItem label="TypeScript">

```typescript
import { childrenFrom, eventType, Guid, removedWith, setFrom } from '@cratis/chronicle';
import { field } from '@cratis/fundamentals';

// Events
@eventType()
export class MbRemovalFullShoppingCartCreated {
    customerName = '';
}

@eventType()
export class MbRemovalFullItemAddedToCart {
    itemId: Guid = Guid.empty;
    productName = '';
    price = 0;
}

@eventType()
export class MbRemovalFullItemRemovedFromCart {
    cartId: Guid = Guid.empty;
    itemId: Guid = Guid.empty;
}

@eventType()
export class MbRemovalFullCartCheckedOut {
}

@eventType()
export class MbRemovalFullCartAbandoned {
}

// Read Models
@removedWith(MbRemovalFullItemRemovedFromCart, 'itemId', 'cartId')
export class MbRemovalFullCartItem {
    id: Guid = Guid.empty;

    @setFrom(MbRemovalFullItemAddedToCart, 'productName')
    product = '';

    @setFrom(MbRemovalFullItemAddedToCart, 'price')
    price = 0;
}

@removedWith(MbRemovalFullCartCheckedOut)
@removedWith(MbRemovalFullCartAbandoned)
export class MbRemovalFullShoppingCart {
    id: Guid = Guid.empty;

    @setFrom(MbRemovalFullShoppingCartCreated, 'customerName')
    customer = '';

    @childrenFrom(MbRemovalFullItemAddedToCart, 'itemId')
    @field(Array, { genericArguments: [MbRemovalFullCartItem] }) items: MbRemovalFullCartItem[] = [];
}
```

[View TypeScript snippet source on GitHub](https://github.com/Cratis/Chronicle.TypeScript/blob/main/Documentation/client-snippets/projections/model-bound/removal/full-example.md)

</TabItem>
</Tabs>

## Best Practices

1. **Use class-level removal** for root read models to keep the removal logic with the model definition
2. **Choose property vs class-level removal for children** based on where the logic fits best:
   - Property-level if the removal is specific to how the child is used in that parent
   - Class-level if the removal logic applies universally to that child type
3. **Always specify keys explicitly** when the default EventSourceId doesn't apply
4. **Use RemovedWithJoin only for children** when a removal event comes from another stream; root removal through a join is not supported
5. **Combine multiple removal attributes** when a model can be removed by different events
