Skip to main content

Entity Schema

Entities are the data model of a Marketplace App. Each entity YAML file under entities/ declares a named type with typed fields, optional status-event mappings, and human-readable code allocation. The runtime interprets this metadata to provide storage, validation, UI rendering, and flow integration — no custom code required.

File structure

entities/property.yaml
id: property # optional — defaults to filename
name: Property # display name
description: Listable property # one-line summary
allocate_code: # optional auto-code allocation
prefix: P-
start: 1001
fields:
- name: title
type: string
required: true
- name: price
type: float
- name: status
type: enum
enum_values: [draft, listed, sold]
status_events: # optional status → event map
listed: property.status_changed
sold: property.status_changed
Top-level keyTypeRequiredDescription
idstringNoEntity id; defaults to the filename without .yaml. Must match the id used in manifest.yamlentities: and in flow tool references.
namestringYesHuman-readable display name for portal and UI.
descriptionstringNoOne-line summary shown in tool-tips and entity pickers.
scopestringNocustomer scopes records to the authenticated Person — see Customer scoping.
allocate_codeobjectNoAuto-generates human-readable codes — see Code allocation.
fieldslistYesOrdered field definitions — see Field types.
status_eventsmapNoMaps status values to business event names — see Status events.

Field types

Every field has a type that controls validation, storage, UI rendering, and flow behavior.

string

Free-form text. Stored as-is. Rendered as a single-line text input.

- name: title
type: string
required: true

integer

Whole number. Stored as JSON integer. Rendered as a numeric input.

- name: bedrooms
type: integer

float

Decimal number. Stored as JSON number. Rendered as a numeric input.

- name: price
type: float

date

Calendar date (no time). Stored as ISO 8601 date string (2026-09-15). Rendered as a date picker.

- name: date
type: date
required: true

datetime

Date and time. Stored as ISO 8601 datetime string. Rendered as a date-time picker.

- name: scheduled_at
type: datetime

enum

Constrained set of values. The runtime validates input against enum_values and renders a dropdown or button group.

- name: status
type: enum
enum_values:
- draft
- listed
- under_offer
- sold
- withdrawn
PropertyTypeRequiredDescription
enum_valuesstring[]YesAllowed values. First value is the default for new records.

image

URL reference to an uploaded image. The runtime handles media upload via the solution UI and stores the URL. In flows, image fields are detected for channel delivery (WhatsApp, widget) — see Image delivery in flows.

- name: image_url
type: image
description: Primary property photo

person

Link to a Customer Hub Person. This is the identity field that enables customer scoping — see Person fields and identity.

- name: person_id
type: person
description: Existing Qefro Person identity (Customer Hub)
ref_entity: person
PropertyTypeRequiredDescription
ref_entitystringYesMust be person. Links to the Customer Hub Person table.

relation

Cross-entity reference. Stores a reference to another entity record by code or UUID. The runtime can expand relations into full objects for trusted queries — see Relation fields.

- name: travel_package
type: relation
required: true
description: Travel package reference
ref_entity: travel_package
PropertyTypeRequiredDescription
ref_entitystringYesTarget entity id. Must match an entity declared in the same app.

email

Email address. Validated as email format. Rendered as an email input.

- name: email
type: email

phone

Phone number. Stored as-is. Rendered as a tel: link in UI.

- name: phone
type: phone

url

URL reference. Stored as-is. Rendered as a clickable link.

- name: document_url
type: url

Field property reference

PropertyTypeApplies toDescription
namestringAllField identifier. snake_case. Used in flow input_map and UI bindings.
typestringAllField type — see types above.
requiredbooleanAllIf true, the runtime rejects records missing this field.
descriptionstringAllDisplay text for UI labels and tool-tips.
enum_valuesstring[]enumAllowed values.
ref_entitystringperson, relationTarget entity for the reference.

Person fields and identity

The person field type links a record to a Customer Hub Person — the authenticated individual interacting through WhatsApp, widget, or API.

Who vs what

A critical distinction in Qefro:

  • WHO = Customer Hub Person (person_id) — the authenticated individual. The runtime injects this automatically from the conversation's verified identity.
  • WHAT = Business entity (Lead, Booking, Reservation) — the business record created during a flow.
# viewing.yaml — the business entity
scope: customer
fields:
- name: property_title
type: string
- name: person_id # WHO — injected by runtime
type: person
ref_entity: person

The runtime never trusts person_id, customer_id, email, or phone values supplied by the LLM or user input. Identity comes from the authenticated session only.

Automatic identity injection

When a flow creates a customer-scoped record, the runtime injects person_id from the authenticated context. The flow does not need to collect or pass this value — it is added automatically by the RuntimeAdapter.

# Flow input_map does NOT include person_id
input_map:
property_title: property_title
date: date
time: time
# person_id is injected by runtime from auth context

Relation fields

Relations link one entity to another within the same app. The runtime resolves references by code (human-readable id like P-1001) or UUID.

Defining a relation

# booking.yaml
fields:
- name: traveler
type: relation
required: true
ref_entity: traveler # target entity
- name: travel_package
type: relation
required: true
ref_entity: travel_package
- name: assigned_agent
type: relation
ref_entity: agent

How references work

  1. Create: Pass the target entity's code (e.g., TVL-1001) or UUID. The runtime resolves to the canonical UUID reference.
  2. Read: The stored value is the target record's UUID.
  3. Expand: Trusted flow constants can request relation expansion, which returns the full target object alongside the reference.

Trust boundary

Relation expansion is trusted metadata — configured in the flow's constants block, not in user input. The runtime strips any expand directive from LLM-generated parameters and only honors expansions declared in the flow's trusted constants.

# Trusted expansion in flow constants
constants:
expand: traveler,travel_package

See Runtime Execution for details on the trust boundary.

Image delivery in flows

Fields with type: image are detected by the runtime for channel delivery. When a flow's message step follows a tool step that returned records with image fields, the runtime extracts the image URLs and delivers them via the appropriate channel:

  • WhatsApp: Sends the image as a media message
  • Widget: Renders the image inline in the chat

The runtime determines which fields are images from the entity metadata — flows do not need to handle media explicitly.

# property entity
fields:
- name: image_url
type: image # runtime detects this for media delivery

# flow message step
- id: show_results
type: message
message: |
Here are matching properties:
{{items_text}}
# Images from property.image_url are delivered automatically

Status events

Entities can map status transitions to business events. When a record's status field changes, the runtime emits the corresponding event.

status_events:
listed: property.status_changed
under_offer: property.status_changed
sold: property.status_changed
withdrawn: property.status_changed
Status valueEvent emitted
listedproperty.status_changed
under_offerproperty.status_changed
soldproperty.status_changed

The runtime also emits standard mutation events:

OperationEvent
Create{entity}.created
Update{entity}.updated
Update with status change{entity}.updated + status event
Delete{entity}.cancelled

All events must be declared in manifest.yamlevents:.

Code allocation

Human-readable codes are auto-generated for entities that declare allocate_code. Codes appear in the UI and are used as references in flows and relations.

allocate_code:
prefix: P- # code prefix
start: 1001 # first number
EntityPrefixExample code
PropertyP-P-1001
ViewingVW-VW-1001
AppointmentAP-AP-1001
BookingBK-BK-1001
ReservationR-R-1001

Codes are immutable once allocated. The runtime increments the counter for each new record.

Customer scoping

Entities with scope: customer are automatically scoped to the authenticated Person. The runtime enforces this at the storage layer:

  • List: Only returns records where person_id matches the caller
  • Create: Automatically injects person_id from auth context
  • Get/Update/Delete: Verifies the record belongs to the caller
id: viewing
scope: customer # enables person-scoping
fields:
- name: person_id
type: person
ref_entity: person
# ... other fields

Rules:

  1. Customer-scoped entities must declare a person field.
  2. The runtime identifies the person field via entity metadata — it does not hardcode person_id as a field name.
  3. Portal users (staff) bypass person-scoping — they can access all records in their workspace.
  4. WhatsApp and widget users are always person-scoped.

Complete example

entities/viewing.yaml
id: viewing
scope: customer
name: Viewing
description: Property viewing appointment bound to Person
allocate_code:
prefix: VW-
start: 1001
fields:
- name: property_title
type: string
required: true
- name: lead_name
type: string
description: Display name (auto-filled from Person.name if available)
- name: agent_name
type: string
- name: scheduled_at
type: datetime
- name: date
type: date
required: true
- name: time
type: string
- name: status
type: enum
enum_values:
- scheduled
- confirmed
- completed
- cancelled
- no_show
- name: notes
type: string
- name: person_id
type: person
description: Existing Qefro Person identity (Customer Hub)
ref_entity: person
status_events:
confirmed: viewing.updated
completed: viewing.updated
cancelled: viewing.cancelled
no_show: viewing.updated