Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / Enum

enum

enum <Name> { <Member>, <Member>, … } [Type(string)] enum <Name> { … } // persists the NAME, not a number

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(string)] — which is what you want when a human or another system will read the column.

stable3 examples compiled by CIenummodeltypes

Summary#

An enum is a fixed set of named values — Draft, Placed, Shipped. Use one wherever a member has a known, closed set of states. The compiler then knows every possible value, so a typo is an error and a switch over it can be checked for completeness.

Signature#

enum <Name> { <Member>, <Member>, … }

[Type(string)]                       // persist the member's NAME instead of a number
enum <Name> { <Member>, … }

Description#

How do I declare one and set it on a row?#

An enum is declared at the top level and used as a member type:

enum OrderStatus { Draft, Placed, Shipped, Cancelled }

entity Order {
  [Required] string Code;
  OrderStatus Status;
}

void Place(string code) {
  var o = Order.Single(x => x.Code == code);
  o.Status = OrderStatus.Placed;      // always qualified — OrderStatus.Placed, never "Placed"
}

int PlacedCount() {
  return Order.Where(o => o.Status == OrderStatus.Placed).ToList().Count;
}

You always write the member qualified — OrderStatus.Placed — so a misspelling is a compile error rather than a string that silently matches nothing.

Is it stored as a number or as its name?#

By default an enum member is stored as a number: Draft is 0, Placed is 1. That is compact, and it has a sharp edge — the number means nothing on its own. Anyone reading the table directly, exporting it, or pointing a reporting tool at it sees 1 and has to come back to the source to learn what that means. Worse, reordering the members silently changes what the stored rows mean.

The [Type] attribute chooses the storage. [Type(string)] stores the member's name instead of a number:

[Type(string)]
enum Priority { Low, Normal, Rush }

entity Ticket {
  [Required] string Title;
  Priority Priority;
}

Now the column holds "Rush". It costs a few bytes per row and buys you a table that explains itself, an export that a human can read, and the freedom to reorder the members without rewriting history.

Reach for [Type(string)] whenever anything outside the app will read the column — a report, an export, an integration, a support engineer at 3am. Keep the default numeric form for values that are purely internal.

How do I loop over every member? — .Members#

TheEnum.Members is every member of an enum, in declaration order — the array a picker, a filter bar or a set of tabs is built from. It reads the type, so it can never drift from it: add a member and every list built this way grows with it.

Its element IS the enum valueRoom.Members is a Room[], not a list of anything wrapping one. So r below compares with == against a Room field, passes to anything taking a Room, and r.Label is a read on the value itself. Enum.GetValues<Room>() is the C# spelling of the same array, and compiles too.

enum Room { [Label("Living room")] LivingRoom, Kitchen }  // NO [Label] → .Label IS the bare name: "LivingRoom"

// WHERE THE LABEL APPEARS BY ITSELF, and where it does not — the difference is the SLOT, not the value.
Text(kiln.Room);                     // a text slot reads the [Label] label — nothing to write
Badge(kiln.Room.Label, tone: t);     // a `string` PARAMETER takes a string, so say .Label
Text("in " + kiln.Room);             // ⚠ a CONCAT is a string operation: "in LivingRoom", the raw member name.
Row { Text("in "); Text(kiln.Room); }   // …so put the enum in its own text slot when you want the label

// A MEMBER MAY SHARE ITS TYPE'S NAME, exactly as in C#. `Room Room;` is fine and needs no second word.
entity Kiln { Room Room; }

foreach (var r in Room.Members) { Tab(r.Label, selected: r == room, onPress: () => Show(r)); }

Dropdown("Room", value: kiln.Room)            // a dropdown over an enum needs no options at all — see [Osyrin.Ui (the UI kit)](/reference/ui/kit/)

The label trap, in one table — the same value reads two different ways depending on the SLOT it lands in:

you writeyou getwhy
Text(p.Room)Living room — the labela text slot RENDERS an enum value, so [Label] applies
Text("in " + p.Room)in LivingRoom — the member name+ is a string op, and an enum's string form is its name (C#-exact). It compiles; it just shows the wrong words
Text("in " + p.Room.Label)in Living room.Label is the read that hands you the label as a string
Badge(p.Room.Label, tone: t)Living rooma string PARAMETER takes a string, so say .Label

.Label is a property, not a methodr.Label, never r.Label(). Full story: [Label], [Icon], [Tone] — what a human reads.

.Label on a member is what a person reads (the [Label] text, or the member's own name — see [Label], [Icon], [Tone] — what a human reads). A generic component can take the same array as a default: component Picker<T>(Binding<T> value, T[] rows = T.Members) — see generic component.

How do I add an "All" option to an enum filter?#

An enum is a closed set, so "no filter" is not one of its members — it is the absence of a choice. Hold the choice in a T? and let null mean all; the extra tab is then an ordinary tab whose selected: asks whether anything is chosen, and the filter is one ||:

enum Room { Kitchen, Bathroom, Bedroom }

entity Kiln {
  [Required, MaxLength(80)] string Name;
  [Required] Room Where;
  security { allow read, create when IsAnonymous || IsAuthenticated; }
}

[Page("/kilns")]
[AllowAnonymous]
[Render(CSR)]
[Title("Kilns")]
component Kilns() {
  live var kilns = Kiln.OrderBy(p => p.Name);
  Room? only = null;                                  // null IS "All" — the absence of a choice
  action Show(Room r) { only = r; }
  action ShowAll() { only = null; }

  render {
    Tabs {
      Tab("All", selected: only == null, onPress: ShowAll);
      foreach (var r in Room.Members) { Tab(r.Label, selected: only == r, onPress: () => Show(r)); }
    }
    foreach (var p in kilns) {
      if (only == null || p.Where == only) { Text(p.Name); }
    }
  }
}

The values come from data — should this be an enum?#

The set is fixed at compile time. If the values come from data — a list of categories an administrator maintains — that is not an enum; it is an entity with rows.

See also#

Related

entity members

The typed members an entity holds — text, numbers, dates, booleans, Guids, enums and references. A member's type…

entity

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

switch

Branch on a value against constant case labels. Only the matched section runs (no fall-through); a default section…

[Label], [Icon], [Tone] — what a human reads

An enum member stores a compact value but shows a human-readable label. [Label("…")] gives a member its label…

When your name is already the platform's

The platform puts 82 ordinary English words in scope in every app with no `using` — `Slot`, `Group`, `Match`, `Point`…