A business-oriented declarative language for specifying the desired functionality of an information system.
A screenplay is the one document a production works from — it names the cast, sets every scene, and writes
every line, so the director, the actors, and the crew all put on the same show. That's the whole idea. A
Screenplay .play file is the script for an information system: its concepts, events, commands, queries,
projections, specifications, automations, and the rules that govern them — top to bottom, in one place.
Screenplay owns that language and its portable meaning; it does not prescribe the runtime. Downstream tools can interpret the same model: Stage is being built to run and render it, while Studio visualizes and edits it. The goal is one script with explicit capability checks, not a claim that every downstream surface already performs every construct.
Four reasons, and they all line up:
- It's the script for the whole show. A screenplay holds an entire production in one document — cast,
scenes, stage directions, dialogue. A
.playfile holds an entire system the same way: nothing about the behavior hides in another layer or another file. - It's written to be performed, not just read. A screenplay isn't the finished film — it's the thing you perform. Screenplay now owns the versioned semantic foundation that runtimes and renderers are being migrated to consume. The language is moving from descriptive syntax to verifiable execution without making one runtime authoritative.
- The
.playextension wears it on its sleeve. A screenplay is a play; the file is a.play. - The Cratis storytelling family. Cratis names its products after telling a story: Chronicle records what happened, Arc shapes the plot, Narrator, Lens, Studio, Prompter… Screenplay is the script the whole cast performs from. It joins the ensemble.
A .play file reads top to bottom like a script — indentation-based, no braces, each construct owning
everything beneath it:
module Invoicing
feature InvoiceManagement
slice StateChange RegisterInvoice
command RegisterInvoice
invoiceId InvoiceId
invoiceNumber InvoiceNumber
dueDate Date
authorize CanManageInvoice
validate
invoiceNumber matches "^INV-[0-9]{6}$" message "Must look like INV-000000"
dueDate > today message "Due date must be in the future"
produces InvoiceRegistered
invoiceId = invoiceId
invoiceNumber = invoiceNumber
dueDate = dueDate
registeredAt = $context.occurred
event InvoiceRegistered
invoiceId InvoiceId
invoiceNumber InvoiceNumber
dueDate Date
registeredAt DateTime
slice StateView InvoiceList
query ListInvoices => InvoiceListReadModel[]
projection InvoiceList => InvoiceListReadModel
from InvoiceRegistered key invoiceId
invoiceNumber = invoiceNumber
status = "draft"
screen InvoiceList
data InvoiceListReadModel[] via query ListInvoices
action RegisterInvoice
One slice, backend to screen: who's allowed in, what has to be true, the fact it records, and the list that shows it — all in a single read. The second slice never touches a database or a controller; it declares how events project into a read model and how that read model appears on screen.
A .play describes an entire system as a set of typed slices, aligned with Event Modeling's vocabulary.
Pick the slice type by what the slice does:
| Slice type | The scene it plays | Constructs |
|---|---|---|
StateChange | something changes the system | command → event via produces or an imperative handler, with validate, authorize, constraint |
StateView | something reads the system | query + projection + screen |
Automation | something runs when something happens | reaction |
Translate | something turns outside data into events | capture |
Three ideas keep the script both readable and complete:
- Declarative first, with bounded escape hatches. The behavior remains meaningful without implementation
files. Selected implementation points — such as a command handler, query performer, or rule predicate —
can carry inline code or a
filereference. These bodies are realization attachments, not a second application model, and the language is evolving toward typed, capability-limited contexts. - Concepts carry compliance. Value types declare their attributes once —
@pii,@sensitive— and every usage inherits them, so GDPR and sensitivity travel with the data instead of being re-litigated per field. - First-class sub-languages. Projections use the Projection Declaration Language (PDL) and captures use the Change Data Capture Language (CDL). Both are independently consumable built-in grammars. Inline language tags are extensible and carried as opaque text; host-language construct keywords remain closed so the compiler never silently discards an unknown behavior.
The full construct reference and the complete EBNF grammar live in
Documentation/screenplay.
The .play file is the single source of truth. The tooling in this repo is the writing room — it makes the
script a joy to author — and downstream, Stage and Studio each read the very same file:
flowchart LR
Author["✍️ you<br/>write the script"] -->|".play"| Play[["📄 Screenplay<br/>one whole system"]]
Tools["🧰 language service<br/>VS Code · editor · Monaco"] -.->|"highlight · IntelliSense<br/>hover · diagnostics"| Play
Play -->|"interpreted by"| Stage["🎬 Stage"]
Play -->|"read by"| Studio["🎨 Studio"]
Stage --> App["▶️ a live application"]
Studio --> Viz["🖼️ diagrams + generated code"]
The intended contract is simple: change the semantic model and every conforming performance changes with it. Until a runtime declares and passes that capability, it must fail closed rather than silently omit or weaken what the script says.
Screenplay lives here — the language definition, its documentation, and the tools that make writing .play
files pleasant:
| Piece | What it is | Where |
|---|---|---|
| Language & grammar | The language reference for every construct and the full EBNF grammar | Documentation/screenplay |
Cratis.Screenplay | The .NET compiler — parsing, the shared syntax tree, visitors and tree traversal, diagnostics, file/folder compilation, and the versioned executable semantic model foundation | Source/DotNET/Screenplay |
Cratis.Screenplay.Tool | The screenplay CLI (a dotnet tool) — verifies every .play file in a directory tree | Source/DotNET/Tool |
@cratis/screenplay-language | Monaco language service — highlighting (incl. embedded C#/TS/React/HTML and PDL/CDL), IntelliSense, hover, diagnostics | Source/Screenplay/Monaco/screenplay-language |
screenplay-editor | A standalone editor host for writing .play files right in the browser | Source/Screenplay/Monaco/screenplay-editor |
screenplay (VS Code extension) | The same language support in VS Code — .play files even get the Cratis icon | Source/Screenplay/VSCodeExtension |
The compiler ships on NuGet. Install the CLI as a global dotnet tool and run it from the root of any
project — it finds every file matching **/*.play, compiles them, and prints any problems compiler-style
with the offending line and a caret:
dotnet tool install -g Cratis.Screenplay.Tool
screenplay # or: screenplay path/to/screenplaysEmbedding the compiler in your own tooling is one package away — see Compiler and CLI:
dotnet add package Cratis.Screenplayyarn install
yarn build
yarn dev # opens the standalone editor on http://localhost:9200Prefer to write in your own editor? Press F5 in VS Code — it builds the language service and the extension
and launches an Extension Development Host with full .play support, ready to try on a sample from
screenplay-editor/samples.
Documentation/screenplay— the language overview, design principles, and top-level structure. Start here to learn the language.Documentation/screenplay/slices.md— modules, features, and the four slice types.Documentation/screenplay/grammar.md— the complete EBNF grammar.Documentation/screenplay/sub-languages.md— how PDL, CDL, and your own sub-languages plug in.Source/Screenplay/Monaco/screenplay-language/README.md— embedding the language service in a Monaco editor.
yarn build # every workspace builds clean
yarn lint # zero lint errors
yarn compile # zero TypeScript errors
dotnet build Screenplay.slnx --configuration Release # zero errors, zero warnings
dotnet test Screenplay.slnx # all specs passPart of the Cratis platform · Licensed under the MIT license