Osy#betaa language · its runtime Osyrin · a hosted platform
Why Osy#Built for agentsAgents as declarationsWorkflows that waitResumes without repeatingSecure by defaultNothing to mockThe editor is the compilerUI in the languageDocuments are dataOne program

Reference / Entity

entity members

<attributes> <Type> <Name>; // inside an entity

The typed members an entity holds — text, numbers, dates, booleans, Guids, enums and references. A member's type spelling carries its nullability: a bare `string`, enum, `DateTime` or `Guid` is REQUIRED, a bare value type reads its zero, a reference is optional, and `?` makes any member optional. See [[types-optional-and-required]].

stable7 examples compiled by CIentitymodeltypes

Summary#

An entity's members are declared C#-style — decimal Total; — one per line, with optional attributes in front. Each becomes a column. Whether a member is required or optional is carried by its type spelling, not a separate keyword: a bare string, enum, DateTime or Guid is required, a bare value type (int, bool, decimal) reads its zero, a reference is optional, and the ? suffix makes any member optional. The full model — and when a required member is checked — is Optional and required members; the essentials are below.

Signature#

entity <Name> {
  <attributes>? <Type> <MemberName>;
}

Description#

Which types may a member hold?#

This table is the whole storable set — Every type, in one list is the same list with the non-storable types (the collections, Action/Func, the component-parameter wrappers) beside it. If a type is not here and you did not declare it yourself, an entity cannot hold it.

TypeHoldsNotes
stringtextgive it a [MaxLength(n)] — see below
int · longwhole numberslong for ids and counters past 2³¹ — long
doublemeasurementsa binary float — never money
decimalmoney and exact quantitiesuse this for money, never a floating type — decimal
booltrue / false
DateTimea point in timestored UTC — DateTime
DateOnlya calendar date, no timea SQL date — DateOnly and TimeOnly
TimeOnlya time of day, no datea SQL time — DateOnly and TimeOnly
TimeSpana durationTimeSpan (durations)
Guidan identifier
Jsonan arbitrary JSON documentfor genuinely open-shaped data — Json
Markdowna section-addressable markdown documenttext plus a rendering contract — Markdown
RichTextformatted prosea rich-text document
Vectoran embedding, for similarity search[MaxLength(n)] sets the dimensions — [Searchable]
Zonean IANA time-zone token (Europe/Stockholm)Time zones — the Zone type and its operations
Culturea BCP-47 culture token (sv-SE)Culture formatting — ToString(format, culture)
byte[]binarythe one array form that is a scalar, not a collection
an enum you declareda fixed set of valuessee enum
another entitya reference to one rowsee relations
<Entity>[]the children pointing back at this rowsee relations
entity Invoice {
  [MaxLength(40)] string Number;
  decimal Total;                 // money is ALWAYS decimal
  int LineCount;
  bool Paid;
  DateTime IssuedAt;
  Guid ExternalRef;
}

The date/time trio and Json are ordinary columns like any other — there is no conversion to write and no DateTime to fall back on. Reach for DateOnly when the time of day is not a fact about the row (a birthday, an invoice date, the day a slot is booked for), and TimeOnly when the date is not (opening hours, a daily cutoff):

entity Appointment {
  DateOnly Day;                  // a calendar date — no time of day to get wrong
  TimeOnly StartsAt;             // a time of day — no date attached
  TimeSpan Runs;                 // how long it lasts
  Json Extras;                   // genuinely open-shaped data, stored as a document
}

DateOnly WhenIsIt(Appointment a) { return a.Day; }   // and it reads back as itself

A member may be named for its own type, exactly as Color Color; is in C# — and it is the natural spelling for the commonest fields (Room Room, Status Status, Type Type). No prefix, no suffix, no second word:

```osy title="a member named for its own type — C#'s Color Color" test app=entity-properties enum Room { Kitchen, Bathroom, Bedroom }

entity Kiln { [Required, MaxLength(80)] string Name; [Required] Room Room; // the member and its type share a name — legal, and the name to use }

Room WhereIsIt(Kiln p) { return p.Room; } // and it reads back with no ceremony


### Is this member required, or may it be null?   {#optional}
Whether a member is required is decided by **how you spell its type** — there is no separate keyword. What a *bare*
(non-`?`) member means depends on whether its type has a natural zero (full treatment: [Optional and required members](/reference/types/optional-and-required/)):

- A **bare `string`, enum, `DateTime`/`DateOnly`/`TimeOnly`, `Guid` or `Json`** — a type with **no honest zero** — is
  **required**: the platform invents no value for it, so you must supply one. (An enum is *not* silently defaulted to
  its first member — reordering the members would change the stored default — so a bare enum is required too.)
- A **bare value-type scalar** — `int`, `long`, `bool`, `decimal`, `TimeSpan` — reads its **zero** (`0`, `false`, `0m`)
  when unset, exactly as a C# field does. It is never null; declare it `int?` for a real "unset". (Because it is never
  null, comparing one to `null` — `priority == null` — is a compile error that points you at the `?` form.)
- A **bare entity reference is optional** (reads back `null`) — the everyday shape is *create the row, then pick the
  related record* — and you write `[Required]` to demand one.
- The **`?` suffix** makes any member optional; it reads back `null` when nobody set it.

**When is a required member checked?** An entity is a **draft until commit**, so `new Ticket {}` compiles — you seed an
empty draft, bind each field to a form input, and the required members are validated **at commit**, naming any that are
still unset. (A `class` has no commit step, so its required members are checked at `new` instead — see
[class properties](/reference/class/properties/).) This is what makes the everyday create-form work.

```osy title="required by spelling; optional with ?" test app=entity-properties
entity Contact {
  string Name;              // REQUIRED — a bare string has no honest zero (checked at commit)
  string? Phone;            // optional — a contact without a phone is fine; reads back null
  DateTime? LastSpokeAt;    // `?` is not a string thing: ANY type takes it, value types included
  int? Doorstep;            // `int?` is genuinely absent, which is what `0` could never say
}

string PhoneOrDash(Contact c) {
  return c.Phone ?? "—";    // null-coalescing on the optional field, exactly as in C#
}

⚑ EVERY C# NULLABLE TYPE IS SUPPORTED. ? is not a string affordance — it is the C# rule, and it holds for value types and reference types alike: DateTime?, int?, decimal?, bool?, Guid?, TimeOnly?, an enum, your own class. There is no list to check against, which is why this page states the rule rather than enumerating one.

⚠ It is written down because the shape is usually only ever SHOWN on a string, and a reader who has seen string? and nothing else has to guess. The guess costs a field: measured 2026-09-01, a model reasoning aloud — "DateTime? — is nullable DateTime supported? The docs mention string? for nullable" — and it dropped the field rather than find out.

A bare reference is the exception — optional by default, so write [Required] to demand one: [Required] Customer Reporter;. See relations and Optional and required members.

How do I give a member a default value?#

A member may declare a default, which applies when the row is created without one — = true, = 0m, or an enum member. It is the honest way to say "this is what a new one looks like", instead of remembering to set it at every creation site:

enum AccountStatus { Active, Suspended, Closed }

entity Account {
  [Required, MaxLength(200)] string Name;
  bool IsActive = true;                          // a new account is active
  AccountStatus Status = AccountStatus.Active;   // …and its status says so
  decimal Balance = 0m;
  [Required] string ApiKey = Security.RandomId(32);   // a fresh unguessable key per account
  DateTime CreatedFor = DateTime.UtcNow.AddDays(30);     // …and a computed date, evaluated at creation
}

void Open(string name) {
  var a = new Account { Name = name };
  // IsActive is true, Status is Active, Balance is 0 — none of them written here;
  // ApiKey is a fresh 32-char id and CreatedFor is 30 days out — each EVALUATED for this new row
}

A default is not limited to a constant. It can be any expression — a call like Security.RandomId(32), a computed value like DateTime.UtcNow.AddDays(30), arithmetic — and it is evaluated afresh for each row at creation, exactly like a C# field initializer. So two accounts opened in the same breath get two different ApiKeys; the expression runs per row, not once. (A constant/enum default behaves the same as always.)

Reach for a default whenever "unset" and "the normal value" are the same thing. It removes a whole class of bug: the creation site somebody added last week that forgot to set IsActive, and the row that has been invisible ever since.

Text needs a length#

string with no [MaxLength] is unbounded. That is fine for a body of prose and wrong for a code, a name or a status — give those a length, and the database enforces it:

entity Article {
  [MaxLength(200)] string Title;   // bounded — a title has a sane maximum
  string Body;                     // unbounded — prose
}

Money is decimal#

There is no float or double member type, and that is on purpose: binary floating point cannot represent 0.10, so totals drift by fractions of a cent and eventually a customer notices. decimal is exact.

How do I index a member, or rename its column?#

A handful of attributes shape how a member is stored rather than what it may hold. Most rows never need them:

  • [Index] asks the database to index the member, so queries that filter or sort by it stay fast as the table grows. Put it on the members you actually query by.
  • [ExternalName] overrides the database column name when it must differ from the member — [ExternalName("col")] maps onto a pre-existing or externally-owned schema.
  • [Virtual] marks a member in-memory only: it is never persisted, for a value you compute and carry but do not store.
  • [Id] marks a member as the entity's primary key. Every entity already has an automatic Id, so you only reach for this to supply your own key instead of the default.
  • [DynamicType] lets a member's type be resolved at runtime rather than fixed at declaration — an advanced escape hatch for genuinely polymorphic storage.
  • [Mentions] declares the entities a rich-text member may @-mention, so an editor can offer and resolve them.
entity LegacyCustomer {
  [Required, Index] string Status;              // queried often → indexed
  [ExternalName("cust_ref")] string Reference;  // the column is named cust_ref in the database
  [Virtual] int ScoreThisSession;               // computed and carried, never stored
}

See also#

  • entity — the type these members live in
  • constraints — [Required], [Unique], [MaxLength], [Min]/[Max], [Pattern]
  • relations — members that point at another entity
  • enum — a member with a fixed set of values

Related

entity

Declares a persisted type — a table of rows the app stores, queries and secures. Every entity gets an Id and audit…

Optional and required members

A member is required or optional by how you spell its type. A bare value type with a natural zero reads that zero; a…

Every type, in one list

The complete vocabulary of built-in types — the scalars you can store, the collections, the two callable spellings…

DateOnly and TimeOnly

`DateOnly` is a calendar date (no time); `TimeOnly` is a time of day (no date) — the C# types. Build them with `new…

constraints

The per-member rules the database enforces — Required, Unique, MaxLength/MinLength, Min/Max, Pattern, and the…

relations

One entity points at another by declaring it as a member — that is the foreign key. The parent reads its children back…

enum

A fixed set of named values, used as a member type. Stored as a number by default, or as the member's own name with…

type visibility (public / internal)

A top-level type carries a public or internal visibility that decides whether code outside its namespace can name it…