Enums
An enum is a named list of allowed values, each carrying optional display metadata, declared once at schema level and referenced from any number of fields.
What an enum does today
Section titled “What an enum does today”| Capability | Status |
|---|---|
| Declaration, multi-file merge, name resolution | Ships |
Enum names listed by GET /schema | Ships |
| Values rendered into the served exports (JSON Schema, OpenAPI, Zod, CUE) | Ships |
| Protobuf, Avro and OData renderers | Implemented, no route registered — nothing in the service serves them |
Field’s enum reference readable from scripts via the getField service function | Ships |
Write-time membership enforcement from enum: | Not wired — add a validations: entry |
Read-time enrichment of a value into {value, label, color, icon, group} | Implemented, not registered in data.svc |
A database enum type (CREATE TYPE … AS ENUM) | Never emitted on any backend |
| A lookup table per enum, with the entity storing a numeric id | Does not exist — the string value is stored in the column |
| GraphQL enum types in the generated SDL | Not generated — enum fields render as String |
Declaring an enum
Section titled “Declaring an enum”Enums are a top-level list. They are not declared inside an entity.
| Key | Type | Required | Meaning |
|---|---|---|---|
enums[].name | string | Yes | The name a field’s enum: key resolves against. Matching is exact and case-sensitive. |
enums[].description | string | No | Carried on the enum; not emitted by any renderer. |
enums[].values[].value | string | Yes | The literal stored in the column and sent over the wire. |
enums[].values[].label | string | No | Display label. Consumed only by the enrichment hook. |
enums[].values[].description | string | No | Parsed and carried; no consumer. |
enums[].values[].color | string | No | Display colour. Consumed only by the enrichment hook. |
enums[].values[].icon | string | No | Display icon. Consumed only by the enrichment hook. |
enums[].values[].group | string | No | Grouping key. Consumed only by the enrichment hook. |
enums[].values[].deprecated | bool | No | Parsed and carried; no consumer reads it. |
enums: - name: order_status description: Lifecycle of a sales order. values: - value: draft label: Draft color: "#9ca3af" icon: pencil group: open - value: confirmed label: Confirmed color: "#2563eb" group: open - value: shipped label: Shipped group: closed - value: cancelled label: Cancelled deprecated: trueReferencing an enum from a field
Section titled “Referencing an enum from a field”| Key | Type | Effect |
|---|---|---|
enum | string | Names a top-level enum. Resolved by the exporters and by the enrichment hook. |
enumvalues | []string | Inline value list with no metadata. Never enriched on read — there is no metadata to attach. |
entities: - name: sales_order primary-key: id fields: - name: id type: ulid defaultvalue: "ulid()" - name: status type: enum enum: order_status defaultvalue: draft validations: - type: enum values: [draft, confirmed, shipped, cancelled] - name: channel type: enum enumvalues: [web, pos, partner]An enum: naming an enum that no file declares is not a load error. The field loads, and the exporters emit a reference to a definition that is not in the document.
What reaches the database
Section titled “What reaches the database”The column is a plain string column. For the two fields above, the emitted DDL is:
"status" TEXT NOT NULL DEFAULT 'draft',"channel" TEXT NOT NULLPer backend:
| Backend | Column type for enum / state |
|---|---|
| PostgreSQL | TEXT |
| ClickHouse | String |
| DuckDB | VARCHAR(255) |
| SQLite | TEXT |
The deployable service wires PostgreSQL for tenant datastores. The other three dialects exist in the engine but cannot be selected from tenant configuration — no pool factory is registered for ClickHouse, and DuckDB and SQLite are rejected by pool-config validation.
Enforcing values
Section titled “Enforcing values”Membership is enforced by a field validation, not by the enum reference. Three validators cover it:
validations[].type | Behaviour |
|---|---|
enum | Fails when the value is not in values. |
in | Identical to enum. |
notin | Fails when the value is in values. |
- name: status type: enum enum: order_status validations: - type: enum values: [draft, confirmed, shipped, cancelled] message: "status must be a known order status" code: ORDER_STATUS_INVALIDRules that apply to all three:
- They run on create and on update.
- They are skipped when the payload does not carry the field, so a partial update that omits the column is never checked.
- Comparison is equality with a stringified fallback, so the number
1matches the value"1". - Omitting
values:makes the validator pass silently. It is not a load error. - The
values:list is independent of the enum’s values. Nothing checks that the two agree — keeping them in step is your job. - On failure the error carries rule
enumand codeENUM(orIN/NOTIN), overridable per rule withcode:andmessage:. It surfaces through the REST create/update failure envelope; see the HTTP reference. - An unrecognised
validations[].typefails loud at write time withVALIDATION_UNKNOWN— a typo does not silently disable the check.
The schema-validation code INVALID_ENUM_VALUE exists in the error catalogue but has no emitter; you will not see it.
Value metadata: label, colour, icon, group
Section titled “Value metadata: label, colour, icon, group”The enrichment hook replaces an enum field’s raw string with a structured value on read:
{ "status": { "value": "draft", "label": "Draft", "color": "#9ca3af", "icon": "pencil", "group": "open" }}color, icon and group are omitted when empty; description and deprecated are never emitted.
This hook is not registered in the deployable service. The service builds its own hook chain and the engine’s baseline chain registers only defaults, validate and audit, so reads return the raw string. Labels, colours, icons and groups are declared-but-inert unless a host registers the hook itself. Treat them as documentation of intent, or resolve them client-side from an export.
Enums in the typed exports
Section titled “Enums in the typed exports”Enum names are canonicalised to PascalCase wherever a named type is emitted: order_status becomes OrderStatus.
| Export | Named enum renders as |
|---|---|
GET /schema/jsonschema.json | $defs.OrderStatus = {"type":"string","enum":[…]}; the field emits {"$ref":"#/$defs/OrderStatus"} |
GET /schema/openapi.json | components.schemas.OrderStatus; the field emits a $ref |
GET /schema/zod.ts | export const OrderStatusEnum = z.enum([…]) plus an inferred type |
GET /schema/cue | #OrderStatus: "draft" | "shipped" |
GET /schema/graphql | String — no GraphQL enum type is generated |
| Protobuf (no route) | enum OrderStatus with an ORDER_STATUS_UNSPECIFIED = 0 member prepended and values upper-snake-cased |
| Avro (no route) | An enum record whose symbols are the values with every character outside [A-Za-z0-9_] replaced by _ |
| OData (no route) | <EnumType Name="OrderStatus"> with ordinal Value attributes |
The Protobuf, Avro and OData renderers are complete, but no endpoint registers them and nothing else in the service calls them — there is no way to fetch a .proto, .avsc or OData $metadata document. Only the four routed exports above are reachable.
No renderer carries label, color, icon, group, description or deprecated. Exports contain values only.
GET /schema returns the enum names for the tenant as a flat list. The per-entity detail response does not report which enum a field references; use the exports for that.
Merging across files
Section titled “Merging across files”Enums merge by name across every YAML file under the loaded root, last write wins — a later file declaring order_status replaces the earlier one wholesale, with no diagnostic. This is the opposite of entities and traits, which keep the first definition and log the duplicate. There is no deep merge: you cannot add a value to an enum by re-declaring it with one entry.