# Versioning Events

This guide explains how to evolve event types and structures in a **safe, predictable, and maintainable** way. It focuses on practical strategies for versioning events and shows how to preserve compatibility without losing **semantic clarity**. You'll learn when to introduce new event types, how to handle schema changes, and what to avoid when working with **immutable historical data**.

In event-sourced systems, **data is not updated – it is accumulated**. Once an event has been written, it becomes part of the system's **permanent history**. That history must remain interpretable and meaningful, even as the software and business rules around it evolve. **Versioning** is the mechanism that makes this possible. It allows you to adapt your system to new needs without rewriting the past.

## Events Are Immutable and Long-Lived

Because events are **immutable**, they cannot be changed or deleted once written. This immutability ensures a reliable **audit trail** and enables safe, repeatable reprocessing – but it also introduces a constraint: you cannot simply "fix" a structural mistake or adjust an outdated payload.

Instead, your system must continue to support **old event versions** even as new ones are introduced. This creates the need for careful **versioning**, both in naming and in structure. Without a clear strategy, small changes can lead to large **inconsistencies**, broken **projections**, and ambiguous interpretations over time.

## Use Type Names to Express Version Changes

Instead of a dedicated version field in the event format, versioning can be expressed through the **`type` field**. When a structural or semantic change is introduced that **breaks compatibility**, a new type should be created – typically by appending a **version suffix** to the name.

For example:

```
com.example.library.book-acquired.v1
com.example.library.book-acquired.v2
```

This approach makes the version **explicit** and allows different versions of the same conceptual event to coexist in the system. It also makes it easier to define clear **schemas**, write separate **event handlers**, and reason about the behavior of **projections** over time.

Avoid adding version numbers unless there is a **breaking change**. Not every refinement requires a new version. Only when previous consumers would fail – or misunderstand the event – should a new type be introduced.

## What Counts as a Breaking Change?

A change is considered **breaking** if it violates the expectations of existing consumers or projections. Examples include:

- **Renaming or removing existing fields**
- **Changing the type or semantics of a field**
- **Making an optional field required**
- **Introducing a new required field into a registered schema**

These changes may prevent older code from correctly parsing or interpreting the event. They can lead to **runtime errors**, **invalid projections**, or **silent misbehavior**.

**Non-breaking changes**, on the other hand, are those that preserve the meaning and structure expected by existing consumers. These include:

- **Adding new optional fields**
- **Extending enumerations or value sets**
- **Refining internal structure without affecting existing fields**
- **Changing default behavior in a way that does not alter output**

Whenever in doubt, treat a change as **breaking** unless you are certain that all consumers can handle it safely.

## Schema Registration Enforces Compatibility

When an event store lets you register **JSON schemas** for each event type, these schemas are **strictly enforced** once registered. New events that do not match the schema are **rejected**. If existing events are also validated against the schema during registration, you cannot retroactively impose constraints that older events violate.

> **Try it with EventSourcingDB**
>
> EventSourcingDB supports registering JSON schemas for event types and validates existing events during registration – see **[Registering Event Schemas](/learn/tutorials/first-steps-with-eventsourcingdb/registering-event-schemas)**.

This design ensures **structural integrity** but also increases the importance of **versioning**. If you need to add a required field or tighten validation, you cannot simply update the schema. Instead, you must define a **new event type** with a **new schema** and begin producing that version going forward.

This also affects **projections** and **event handlers**: they must be updated to recognize and process the new type separately. This might involve duplicating some logic, but it guarantees that no assumptions are broken silently.

## Transforming Events Without Changing Them

Sometimes you may want to unify multiple event versions in a **projection** or **read model**. For example, both `book-acquired.v1` and `book-acquired.v2` might represent the same underlying concept, but with different field names or payload structures.

In such cases, it is recommended to handle **transformation outside of the event store**. You can use an **adapter layer** or **transformation function** in your projection code to normalize both versions into a common shape. This keeps the **event log pristine** while allowing your application to evolve and simplify its downstream logic.

A query language can also **filter and transform data declaratively** during queries. However, **semantic normalization** is best handled in **application code**, where business rules can be applied explicitly.

> **Try it with EventSourcingDB**
>
> In EventSourcingDB, **[EventQL](/docs/eventsourcingdb/eventql)** filters and transforms data declaratively during queries.

## Designing for Compatibility From the Start

The need for **versioning** cannot be eliminated, but it can be reduced by thoughtful design. When modeling a new event type, consider:

- Whether all fields are truly **required**, or whether some can be **optional**
- Whether a field's name and type are likely to remain **stable**
- Whether the semantics of the event are **clear and unambiguous**

Favor **expressiveness over cleverness**. Include all relevant domain information in the initial version, but avoid **overfitting** the structure to current implementation details. Think about how the event might be used, reinterpreted, or extended in the future.

Also consider how events **relate to one another**. If two types are tightly coupled, changes in one may require changes in the other. In such cases, **version them together**, and ensure **projections are version-aware**.

## Accepting Divergence in the Event Log

It is perfectly acceptable – and often necessary – to have **multiple versions** of the same event type coexisting in the event store. Some streams may contain only old versions, others only new, and some may contain a mix. This reflects the **natural evolution** of a system over time.

Rather than trying to homogenize the log, embrace its **historical accuracy**. Each event tells the story of what was known, decided, and recorded at a particular moment. That **fidelity is a strength**, not a problem – as long as your code is equipped to interpret it correctly.
