Entity metadata contract
GET /api/v1/entities/{entity}/meta returns the entity metadata — a typed
JSON document describing how the entity is displayed and which operations it
supports. The canonical type is EntityMeta in
src/http/entity-meta.types.ts: a closed interface (no index signature),
so any property not declared in the schema is a compile-time error. Every
static meta object (*.meta.ts) MUST be annotated EntityMeta.
Source and assembly
- The static meta declares identity,
display_field,columns, optionaltableoptions, and optionalactions_overrides. assembleMeta(meta, entityClass)injects runtime fragments:display_name(defaulted to"${display_field}"when omitted) andcollaboration(fromgetEntityPersistenceMeta().isAuditable).deriveEntityActions(router, entity, actions_overrides)scans the registered route table and emits theactionsarray.
Anatomy
Code
Root properties
| Field | Where | Required | Purpose |
|---|---|---|---|
entity | static | yes | Entity key, snake_case singular — route segment |
translation_key | static | yes | snake_case singular i18n prefix |
title_key | static | yes | i18n key for the entity title |
uid | static | yes | Column key used as row identity |
display_field | static | yes | Key of the primary display column |
display_name | response | yes | Display template (${field}). Optional in static meta — assembleMeta defaults it to "${display_field}" |
actions_overrides | static | no | Per-op enabled toggles — may only affect existing ops |
columns | static | yes | Root-level field dictionary, transversal to all page types |
table | static | no | EntityListTable options — absent on form-only metas |
actions | response | yes | Derived capability contract |
collaboration | response | yes | { enabled, expose_editing_value } |
display_name is the generic display representation — edit-page titles,
comboboxes, related-object labels. It supports ${field} template syntax
(interpolated client-side) so composed expressions like
"${first_name} ${last_name}" are possible.
columns props sheet (MetaColumn)
| Prop | Required | Purpose |
|---|---|---|
key | yes | Row field key (snake_case) |
label_key | yes | i18n key for header/form label |
type | yes | text | badge | date | datetime | color | boolean | number | sparkline |
order | yes | Explicit position within its rendering group — array position in columns MUST NOT matter. Convention: uuid is order: -1 (canonical first sticky column, hidden but selectable), the display_field column is order: 0 |
sticky | Pinned left — uuid and the display_field column are the standard sticky pair | |
audited | System audit column — rendered last | |
sortable / searchable / filterable / hideable | Capability flags (default true) | |
default_visible | false = selectable but hidden by default | |
badge | { values: Record<string, { label_key?, label_text?, color? }> } | |
datetime_iana_toggle | { record_iana_field } | |
sparkline | type:"sparkline" — { endpoint, request_field, row_key, response_field?, bucket_field?, bucket_unit_field?, series }; virtual column, the list engine batch-POSTs row keys after every load | |
tooltip, tooltip_priority, tooltip_title | Tooltip keys/priority | |
show_form_tooltip, show_list_tooltip | Tooltip visibility per context |
Rendering order derives from flags: sticky → normal → audited, each
sorted by order (orderedColumns on the FE).
table options
| Prop | Purpose |
|---|---|
default_view | table | cards | cards_list |
default_sort | { key, dir } |
default_page_size | Initial rows per page |
page_size_options | Dropdown values |
row_custom_actions | Custom row CTA display config for non-standard ops |
filter_presets | Mutually-exclusive preset scope filters in the toolbar — display: "button_group" renders icon-only buttons (per-option icon required, meaning via title tooltip + aria-pressed; the label is never displayed); "combobox" for long lists. Exactly one option active; where:[] = "all" escape; conditions AND into every /list request |
table is absent on form-only metas (e.g. GET /api/v1/auth/me/meta —
meMeta in auth-session.router.ts is a plain EntityMeta without
table).
actions — standard vocabulary
deriveEntityActions always emits every standard op; missing routes produce
explicit enabled: false entries:
list, meta, get, create.single, update.single, delete.single,
restore.single, read.audit, export, delete.bulk, restore.bulk,
duplicate.single, duplicate.bulk
enabled: true→ op exists; per-user gating viapermissions/sentinelenabled: false→ route absent OR product-disabled viaactions_overrides— CTA hidden everywhereactions_overridesnever invents ops — it can only toggleenabled- Non-standard routes surface as additional bare ops (
change-password,check-availability) - Ops outside
/api/v1/entities/:entity(e.g./api/v1/auth/users) are included via theextraScansparameter
Search semantics
Default "all fields" search covers visible ∩ searchable ∩ text-physical
columns. The BE derives fallback search keys per entity via
src/lib/search-keys.ts — meta searchable flag ∩ DAL pgType
(varchar/text/uuid; uuid uses the existing text cast). JSONB and
other non-text columns MUST be searchable: false.
Specializations
ConfigEntryMeta extends EntityMetaadds requiredtype_capabilities(from@primebrick/sdk) — used byconfig_entryonly.EntityMetaResponseis the served shape:EntityMetaplus requireddisplay_name,actions,collaboration.
Next steps
- API conventions — endpoint taxonomy
- Collaboration — the
collaborationfragment - RBAC — permissions behind
actions[].permissions