---
title: Events
editUrl: https://github.com/Cratis/Screenplay/edit/main/Documentation/screenplay/events.md
description: Events are immutable, past-tense facts — the record of something that happened. An event declaration defines the event type's name and properties.…
---

Events are immutable, past-tense facts — the record of something that happened. An event declaration defines the event type's name and properties. Properties use [concepts](/screenplay/concepts/), composite [types](/screenplay/types/), or primitives.

## Syntax

```screenplay
[public] event <Name> [generation <N>] [from "<origin>"]
  [file <path>]
  [description "<text>"]
  [documentation <fenced markdown>]
  [id "<old-name>"]             // only after renaming a persisted event
  [tag <value>]*
  <property> <Type> [subject]
  ...
```

Without `generation`, an event is generation 1 and prints exactly as before. To show an evolved event, declare **each full shape** with its own number, beginning at 1 and continuing without gaps or duplicates. Declarations with the same name in the same owning slice (module, feature path and slice) identify one event contract: its address and `EventContractId` do not include the generation number. A same-named event in another slice has a different address, not another generation of this contract. The current ESM binder treats same-named events across slices as ambiguous, and the identity catalog rejects colliding legacy IDs; do not use cross-slice declarations to express lineage. Generations use numbers from 1 through 4294967294; Chronicle reserves 4294967295 for an unspecified generation. The previous generation of N is always N−1. References to an event name resolve to its current (highest) generation in that slice; property checks use that shape, not the historical ones. The executable model stores one contract with current revision N and complete prior revisions 1..N−1, each with its predecessor (generation 1 has none). Each revision owns separate property identities, even for same-named properties. A lone `generation 1` marker behaves like an unmarked event and retains its previous ESM version. A multi-generation contract selects ESM v4. Bare references use the current revision only; a `produces` mapping, `then` assertion, projection mapping, or constraint naming a removed property fails binding with `PLAY0273`, and `given` historical-shape consumption reports `PLAY0449` with the event and revision. No historical fact is silently converted or replayed. A target without explicit migration support must reject an evolved contract; Chronicle registration needs migrations, which Screenplay does not yet express. Complete historical declarations alone do not prove schema immutability across edits to referenced concepts or types (see #71).

```screenplay
event ProjectRegistered generation 1
  projectId Uuid
  name String

event ProjectRegistered generation 2
  name String
```

The optional `file` line names the repository relative file this declaration is realized by, so a document can be navigated back to the code it describes. It is additive - it never stands in for any part of the declaration. See [File references](/screenplay/file-references/).

For an existing catalog, advance a contract only with `SemanticIdentityCatalog.PlanEventRevisionAdvancement(previous, expectedRevision, documentKeys, semanticAddresses, eventAddresses, advancements)`. Supply the complete current addresses and an explicit `(event address, new revision)` advancement. The plan checks the exact previous catalog revision, retains the contract id, carries generation-1 property IDs to revision-qualified addresses, and refuses backward or stale advancement. A fresh compile can bootstrap all property identities for each declared revision and record revision N in its returned `SemanticCompilation.Documents.IdentityCatalog`; persist that catalog before subsequent edits. Workspace transactions can supply the same explicit advancement through `WorkspaceTransactionRequest.EventRevisionAdvancements`, checked against `ExpectedCatalogRevision`. Newly introduced events receive their declared revision without an advancement. Declaring fewer generations than a persisted catalog records fails binding.

## Public contracts and origin

Unmarked events remain private local facts. Mark a local contract `public event Shipped`.
An event from another application uses `event Dispatched from "Fulfillment"`; an origin implies
public visibility, so `public event Dispatched from "Fulfillment"` means the same thing.
The origin is an opaque, nonblank string. It is never resolved as a declaration or filesystem
path, and renaming a declaration never rewrites it. Quotes, backslashes and control characters
use the ordinary [string escaping rules](/screenplay/grammar/). When combining origin and generation,
write the generation first: `public event Dispatched generation 1 from "Fulfillment"`.

The C# and TypeScript parsers, printers, typed AST JSON, authoring workspace, Monaco, VS Code and the event model
board preserve these fields. A model that uses public visibility or an origin selects **ESM v9**, released in Screenplay 4.117.0 (see [interoperability](/screenplay/interoperability/)); models that use neither keep their existing version and canonical bytes. The executable model records each event's visibility and origin, and the reference evaluator publishes a public event as a recorded fact when its outbound projection or reducer fires. It does not deliver to another application. An imported public event (one with an origin) declares its shape locally; public or foreign imports that cannot supply a local shape still refuse with `PLAY0268`. Stage and renderers do not realize these contracts yet. See [decision 0049](https://github.com/Cratis/Screenplay/blob/main/decisions/0049-public-events-and-translation-direction.md)
(accepted ruling) and [decision 0051](https://github.com/Cratis/Screenplay/blob/main/decisions/0051-admit-public-events-as-esm-v9.md) (accepted admission) for why the origin is a quoted store name and how direction defaults.
Inline `produces event` keeps its existing syntax; use a standalone declaration for these fields.

The assembled model (C# and TypeScript compilers, both editors) validates contract usage, not just headers. Commands cannot publish public events.
An explicitly outbound Translate slice consumes private local events and produces exactly one local public
contract type. An explicitly inbound Translate slice consumes foreign public contracts and produces private
local events. Other slices cannot consume foreign contracts. A translation declaring or using public metadata
must state its direction; legacy translations without public metadata retain their historical inbound behavior.
An outbound slice publishes its public event with a [projection or reducer that targets it](/screenplay/projections/#projecting-to-an-event)
or a reaction; an inbound slice consumes foreign public events with [`source events`](/screenplay/captures/#capturing-from-public-events)
or a reaction. Both forms select ESM v9 when bound.
Qualified references and imports use declaration scope, not a name-based guess. See
[Public event boundary diagnostics](/screenplay/diagnostics/#public-event-boundaries) for the codes and current construct limits.

## Example

```screenplay
event InvoiceRegistered
  invoiceId     InvoiceId
  customerId    CustomerId
  invoiceNumber InvoiceNumber
  lines         InvoiceLine[]
  currency      CurrencyCode
  registeredAt  DateTime
  status        InvoiceStatus
```

## Authoring metadata

Standalone events and [inline command events](/screenplay/commands/#declare-an-event-inline) accept:

- `description "<text>"`, or `description` followed by a fence tagged `text` or `markdown`;
- `documentation` followed by one nonempty fenced `markdown` block;
- optional `id "<old-name>"` when renaming an event that already has stored events.

Markdown description fences are specific to events, including inline declarations; descriptions on other constructs use `text`. A bare description fence remains accepted with deprecation warning `PLAY0397`.

Each directive can appear at most once. These are authoring metadata: binding reports `PLAY0270`, but they add no ESM bytes. The `id` string does **not** replace or rehash the portable `EventContractId`; catalog identity remains authoritative. Rendering documentation into generated XML or TypeScript comments is a separate downstream capability.

Leave `id` absent for a new event. Cratis convention is plain `[EventType]` without arguments; a pin exists to preserve an old persisted name on a rename, not to decorate a new declaration. A pin equal to the current name reports information diagnostic `PLAY0471` with a typed removal repair.

Workspace and MCP event renames pin the previous effective name by default, because an authoring catalog does not prove whether events have been stored. If you know the event has never been persisted, set `EventNeverPersisted` on the rename request (`eventNeverPersisted: true` in MCP) to omit a new pin. That option also removes a redundant pin equal to the current name (the one `PLAY0471` reports); a pin naming an earlier, different identity is never overwritten. Renaming back to the pinned name removes the pin. Generations must agree on their effective identity; contradictory pins refuse the rename. Command, slice and module renames do not add event pins.

Rename preserves trivia by default, including when inserting or removing the pin. Unsupported source edits refuse rather than silently reformatting the document.

To evolve an inline event, first use `WorkspaceEventRefactorings.ProposeExtractInlineEvent` or MCP `propose-extract-inline-event`. The declaration moves into the slice, keeping its id, description, documentation, tags and property identities. Its production keeps explicit routing or receives the previously implicit identifier destination. Extraction requires unchanged canonical ESM and catalog assignments, preserves comments exactly once or refuses, and needs explicit canonical formatting consent. Add later generations only after extraction; there is no reverse inlining refactoring.

````screenplay
event Renamed
  description "The project acquired another name"
  documentation
    ```markdown
    The project keeps its event source identity.
    ```
  name String
````

Property-shaped lines remain properties: `id String`, `description String`, and `documentation String` still declare payload fields. Inside an inline event they must also provide their mapping, such as `description String = name`.

## Inline declarations

`produces event <Name>` inside a command declares the same slice-owned event as a standalone declaration. The [command reference](/screenplay/commands/#declare-an-event-inline) explains its typed mappings and destination default. Extract an inline event before introducing later generations. Plain productions can reference it regardless of declaration order. The event and its properties have the same workspace addresses and stable identities as the equivalent standalone event in that slice. Inline typed mappings cannot repeat a property name (`PLAY0168`).

## Type modifiers

| Modifier | Syntax | Example |
| --- | --- | --- |
| Collection | `<Type>[]` | `lines InvoiceLine[]` |
| Optional | `<Type> optional` | `note String optional` |

## Data subject

An event has one data subject, defaulting to its event source. When it records data about someone else, mark the property identifying that person with trailing `subject`:

```screenplay
concept CustomerId : Uuid
module Billing
  feature Contact
    slice StateChange ChangeContact
      event CustomerContactChanged
        customerId CustomerId subject
        note String
```

The modifier is last, after any other property modifiers; in an inline event it precedes the mapping `=`. A property named `subject` or of type `subject` remains a property, not a modifier. Each event generation chooses its mark independently. Two marks are errors on each extra property, naming the first; choosing the subject has no automatic repair. The compiler cannot detect subtler mixing of several people's data and must not guess the subject.

Allowed targets are required scalar `String`, `Uuid`, concepts over either, and `Int`-backed concepts, matching the stream-id type set. Bare `Int`, enums, collections, composite types, optional values, `Decimal`, `Bool`, `Date` and `DateTime` (including concepts over them) are refused. Concepts marked `pii` or `secret`, including their aliases and legacy spellings, cannot be subjects: `EventContext.Subject` is stored in plaintext. Use a surrogate identity. Unavailable imported shapes remain unresolved with an unknown-type warning rather than guessed.

An empty `String` value cannot be caught at compile time and Chronicle falls back to the event source; consider `not empty` validation on the identity concept. Equal identity strings across entity kinds share one subject, so favor `Uuid` identities.

**Phase 1 is report-only lineage metadata.** Binding reports `PLAY0270`; canonical executable-model bytes and revision are identical to the unmarked model. No C# `[Subject]` provider output, projection lineage propagation, erasure, export or redaction is promised. Executable admission is a separate phase. The event context's existing `$eventContext.subject` means the event source unless a subject was supplied; this phase does not change its runtime value. `pii` and `secret` concepts still refuse executable binding with `PLAY0268`.

`subject` is refused on commands, composite types, operation inputs, trigger or reaction data and response fields (decision 0008). Read model marks are **not yet supported**; support and Chronicle's reserved `_subject`, `__subject` and `__subjects` names are tracked in [#559](https://github.com/Cratis/Screenplay/issues/559). `secret scope subject` selects encryption scope; policy `matches subject` means the thing acted on, not the caller (`$context.causedBy.subject`).

MCP `declaration-details` event summaries expose `subject: { "source": "eventSource" }` by default, or `{ "source": "property", "property": "customerId" }`. The properties view exposes `isSubject`; `syntax-schema` exposes the optional `PropertySyntax.isSubject` flag, omitted from structural JSON when false. Rename, move and inline extraction preserve the flag.

## Tags

`tag` lines attach tags to every append of the event — Chronicle stores them alongside the event so consumers can filter and group on them. A tag value is a bare identifier or string literal for static tags, or a `$context.` expression for tags resolved from context at append time.

```screenplay
event InvoiceRegistered
  tag invoicing
  tag "billing"
  tag $context.identity.id
  invoiceId InvoiceId
```

Each historical revision retains its declared literal tags; new appends use the current revision's event tags.

Tags inside `produces event` are event-type tags, just like the standalone form. Tags can also be declared per production site — on a plain [`produces` block](/screenplay/commands/#the-produces-block) and on a capture [`append` block](/screenplay/captures/) — where they apply to that specific append rather than every occurrence of the event type. ESM v1 admits literal event-level and production-level tags as append metadata, in that order. Chronicle's `IEventSequence.Append` accepts tags and persists them in `EventContext.Tags` (Decision 0001); tags do not change the event payload. `$context`-computed tags remain syntax-only; ESM v2 admits selected scalar `$context` **produces mappings**, not computed tags.

## Guidance

- **Name events in the past tense** and make them self-describing: `InvoiceRegistered`, never `Created`.
- **One purpose per event.** If an event needs a nullable property to cover two situations, model the second situation as its own event.
- **Compliance is inherited.** A property typed with a `@pii` concept is PII — nothing extra to declare on the event.
- The event-source identity is not an event property; it travels in typed event context in ESM v2. A command's `produces … for <identifier>` binds the destination from a required scalar command identifier. A projection can use `$eventSourceId` without copying that identity into every event payload, and a specification can name it with `for <value>` on its `given` and `then` events. Marking an event property `identifier` is an error.
- **Declare the shapes you reference.** A property typed `InvoiceLine[]` needs a [`type InvoiceLine`](/screenplay/types/); the compiler warns when it resolves against nothing the document declares.
