PrimeBrickPrimeBrick
  • Docs
  • Contact
  • MIT License
  • Documentation
  • MCP Server
  • API Catalog
  • Services
  • Libraries
PrimeBrickPrimeBrick

© 2026 PrimeBrick. MIT License. v3.11.2

github
Backend
Frontend
    Developer Guide
      OverviewGetting startedUI stackApp shell & sidebarAuthentication & sessionsSystem settingsUI componentsUI patternsIcon ConventionsInput AnatomyField Hints & TooltipsButton Style StandardsEntity metadatai18n — Translations on the FrontendThe AI Tool Loop (caller-driven)Error HandlingComposables (use* hooks)
      Components
      API Reference
Microservices
powered by Zudoku
Developer Guide

Entity metadata

Every entity exposes GET /api/v1/entities/{entity}/meta — a typed JSON document that describes how the entity is displayed and what operations it supports. The backend owns the canonical type (EntityMeta in src/http/entity-meta.types.ts); the frontend mirrors the response shape in src/lib/entity-list/types.ts. All fields are snake_case end to end.

Anatomy

Code
{ "entity": "user_profile", "translation_key": "user_profile", "title_key": "system.entities.user_profile.title", "uid": "uuid", "display_field": "display_name", "display_name": "${display_name}", "actions_overrides": { "duplicate.bulk": { "enabled": false } }, "columns": [ { "key": "display_name", "label_key": "system.entities.user_profile.fields.display_name", "type": "text", "order": 0, "sticky": true, "hideable": false, "default_visible": true, "sortable": true, "searchable": true, "filterable": true } ], "table": { "default_view": "table", "default_sort": { "key": "created_at", "dir": "desc" }, "default_page_size": 25, "page_size_options": [10, 25, 50, 100], "row_custom_actions": [ { "action_name": "change_password", "translation_key": "system.settings.users.change_password", "icon": "key-round", "required_permission": "AUTHENTICATED_ADMIN" } ] }, "actions": [ { "op": "list", "enabled": true, "permissions": ["user_profile.read.all"] }, { "op": "create.single", "enabled": true, "permissions": ["user_profile.create.single"] }, { "op": "duplicate.bulk", "enabled": false } ], "collaboration": { "enabled": true, "expose_editing_value": true } }

Root properties

FieldRequiredPurpose
entityyesEntity key, snake_case singular — matches the route segment
translation_keyyessnake_case singular i18n prefix for all entity keys
title_keyyesi18n key for the entity title
uidyesColumn key used as unique row identity
display_fieldyesKey of the primary display column — drives the first/sticky column
display_nameresponseDisplay expression template (${field} syntax). Optional in the static meta — assembleMeta materializes it as "${display_field}" when omitted
actions_overridesnoPer-op enabled escape hatch — may only toggle existing ops, never invents them
columnsyesRoot-level field dictionary shared by all page types (table, form, preview)
tablenoEntityListTable options — absent on form-only metas (e.g. GET /api/v1/auth/me/meta)
actionsresponseDerived capability contract — injected at runtime
collaborationresponseRealtime collaboration flags — injected at runtime

display_name is the generic "how the entity is displayed" representation — edit pages consume it automatically for their title, and the same expression is reusable for comboboxes, related-object labels, and other display contexts.

columns — the field dictionary

MetaColumn describes one entity field. key, label_key, type, and order are required; every other prop is an optional flag:

PropPurpose
orderRequired explicit position — array position in columns does not matter. Rendering groups derive from flags: sticky first, normal next, audited last — each sorted by order. Convention: uuid is order: -1 (first sticky, hidden but selectable), display_field is order: 0
stickyPinned left — uuid and the display_field column are the standard sticky pair
auditedSystem audit column (created_at, updated_by, ...) — rendered last
default_visiblefalse = present in the column selector but hidden by default (e.g. uuid, idp_code)
hideablefalse = cannot be hidden
sortable / searchable / filterableCapability flags — default true, set false explicitly to disable
badge{ values: Record<string, { label_key?, label_text?, color? }> }
datetime_iana_toggle{ record_iana_field } — browser-local vs record-timezone header toggle
sparklinetype:"sparkline" virtual column — { endpoint, request_field, row_key, response_field?, series }; the list engine batch-POSTs row keys after every load (no per-page wiring) and TableCell renders SparklineBars in every view mode (table, cards_list, cards)
tooltip, tooltip_priority, tooltip_titleTooltip content, severity, title keys
show_form_tooltip, show_list_tooltipTooltip visibility per context

table — EntityListTable options

Present only on entities rendered by EntityListTable:

PropPurpose
default_viewInitial view mode: table, cards, or cards_list
default_sort{ key, dir } applied when no user sort is stored
default_page_sizeInitial rows per page
page_size_optionsPage-size dropdown values
row_custom_actionsTable-specific custom row CTAs (non-standard ops)
filter_presetsMutually-exclusive preset scope filters rendered as toolbar controls. display:"button_group" renders icon-only segmented buttons — icon required per option, meaning carried by title tooltip + aria-pressed (the label is never shown); "combobox" for long lists. Exactly one option is always active, where:[] is the "all" escape, conditions AND into every /list request

row_custom_actions entries carry only display config (action_name, translation_key, icon, text_color, disabled_when_deleted, required_permission). Whether the operation exists is still decided by actions — the page wires the handler via customActionHandlers.

actions — the capability contract

The backend derives actions from the registered route table (deriveEntityActions). The array always contains the full standard vocabulary — a missing route produces an explicit { enabled: false } entry rather than an absent op:

list, meta, get, create.single, update.single, delete.single, restore.single, read.audit, export, delete.bulk, restore.bulk, duplicate.single, duplicate.bulk

  • enabled: false → the operation is unavailable for everyone — the UI never renders its CTA, on every page type.
  • enabled: true → rendered subject to per-user permissions/sentinel.
  • actions_overrides[op].enabled = false suppresses an existing op at product level (e.g. hiding a route that exists but should not be exposed).
  • Non-standard routes (change-password, check-availability) surface as additional bare ops.

Every enabled op also carries endpoint — { method, path } resolved from the real registered route (path params use the :param convention, filled from the row's uid field). The frontend dispatches write ops to the declared endpoint, not to a guessed /entities/{entity}/{uuid} convention — this is how user_profile keeps read-only entity routes while its writes live under /api/v1/auth/users/*. Ops without a declared endpoint fall back to the entity convention base (entityApiBase).

This is the single capability/visibility mechanism — there are no parallel row-action boolean flags in the schema.

Search behavior

The default "all fields" search covers visible ∩ searchable ∩ text-physical columns:

  • A hidden-by-default column (uuid) is not searched until the user makes it visible.
  • The scope selector lists all searchable columns, including hidden ones — an explicit selection always wins (a selected hidden column is searched).
  • Non-text columns (jsonb, dates, numbers) are searchable: false and never enter an ILIKE scope.

Writing a new meta

On the backend, annotate the static object with EntityMeta — the closed type rejects unknown properties and missing display_field at compile time:

Code
import type { EntityMeta } from "../../http/entity-meta.types.js"; export const ticketMeta: EntityMeta = { entity: "ticket", translation_key: "ticket", title_key: "system.entities.ticket.title", uid: "uuid", display_field: "title", // display_name omitted — materialized as "${title}" by assembleMeta columns: [ { key: "title", label_key: "system.entities.ticket.fields.title", type: "text", order: 0, sticky: true, hideable: false, sortable: true, searchable: true }, { key: "uuid", label_key: "system.entities.ticket.fields.uuid", type: "text", order: -1, sticky: true, default_visible: false }, { key: "created_at", label_key: "system.entities.ticket.fields.created_at", type: "datetime", order: 90, audited: true, default_visible: false, searchable: false }, ], table: { default_view: "table", default_sort: { key: "created_at", dir: "desc" }, default_page_size: 25, page_size_options: [10, 25, 50, 100], }, };

ConfigEntryMeta extends EntityMeta exists for metas carrying extra required fields (type_capabilities).

Next steps

  • Entity list table — the table component driven by columns + table + actions (routes normally wrap it via EntityTablePage — see entity list pages)
  • Form page layout — form pages consuming columns tooltips and display_name
  • API reference — full prop tables
Last modified on October 28, 2026
Button Style Standardsi18n — Translations on the Frontend
On this page
  • Anatomy
  • Root properties
  • columns — the field dictionary
  • table — EntityListTable options
  • actions — the capability contract
  • Search behavior
  • Writing a new meta
  • Next steps
JSON
TypeScript