UDMI / Docs / Guides / Define Presentation Attributes
This guide explains how to use the embedded JSON schema attributes to control what information is displayed in the UI and how it’s organized.
The entire system is controlled by adding a special object named $presentation
to properties within our schema files. A script reads these special objects to
generate the final UI configuration.
Try out an interactive demo at đź§Ş Presentation Layer Demo
Let’s start with a basic schema for a device. By default, nothing is visible.
device_model.json
{
"title": "Device Model",
"type": "object",
"properties": {
"device_id": {
"type": "string",
"description": "A unique identifier for the device."
},
"last_seen": {
"type": "string",
"description": "The last time the device was online."
}
}
}
Right now, both device_id and last_seen are hidden.
display)To make a property appear, we add $presentation and set "display": "show".
The system is opt-in, so you must explicitly show every property you want to
see.
{
//...
"properties": {
"device_id": {
"type": "string",
"description": "A unique identifier for the device.",
"$presentation": {
"display": "show" // display this property
}
},
"last_seen": {
"type": "string",
"description": "The last time the device was online."
}
}
//...
}
Result:
device_id is now visible in the UI.last_seen remains hidden.section)You can group related properties into sections. The section attribute is
hereditary - meaning child properties automatically belong to the same section
as their parent.
Let’s add a location object and put it in a “Location Details” section.
{
//...
"properties": {
"device_id": {
// ... as before
},
"location": {
"type": "object",
"$presentation": {
"display": "show",
"section": "Location Details"
},
"properties": {
"floor": {
"type": "number",
"$presentation": { "display": "show" }
},
"room": {
"type": "string",
"$presentation": { "display": "show" }
}
}
}
}
//...
}
Result:
location object appears in this section.location.floor and location.room automatically inherit the “Location
Details” section from their parent.display: hide)Setting "display": "hide" on a parent property will hide it and all its
children, no matter what their own settings are. This is useful for hiding
entire blocks of diagnostic data.
Let’s add a diagnostics object that we don’t want to show.
{
//...
"properties": {
"device_id": { /* ... */ },
"location": { /* ... */ },
"diagnostics": {
"type": "object",
"$presentation": {
"display": "hide"
},
"properties": {
"last_error_code": {
"type": "string",
// This 'show' will be IGNORED because the parent is hidden.
"$presentation": { "display": "show" }
}
}
}
}
//...
}
Result:
diagnostics.last_error_code is also hidden, because the parent’s “hide”
setting overrides the child’s “show”.patternProperties)Sometimes, we have objects where the keys are not fixed, like a map of software
versions. We need to tell the system which specific keys from the map we want to
display. This is done with $presentation.paths.
This is for maps where the value is a simple type (like a string).
Let’s add a software_versions map to our device.
{
//...
"properties": {
// ... other properties
"software_versions": {
"type": "object",
"description": "A map of software component versions.",
"patternProperties": {
"^[a-z_]+$": { "type": "string" }
},
"$presentation": {
"paths": {
"firmware": { "style": "bold" },
"operating_system": { "style": "bold" }
}
}
}
}
//...
}
How it works:
firmware, operating_system).{ "style": "bold" }) is a presentation object that
gets applied.Result:
software_versions.firmware
and software_versions.operating_system.This is the most advanced case. It’s used when a map’s value is another complex object, and you want to configure that child object differently depending on the parent key.
Let’s add network_protocols to our device. We’ll have different settings
for bacnet and modbus. This requires two files.
File 1: device_model.json (The Parent)
Here, the $presentation.paths for bacnet and modbus contain an extra
object that will be “injected” into the child schema. Note
the settings.paths key - this is the convention.
{
// In device_model.json
//...
"properties": {
// ... other properties
"network_protocols": {
"type": "object",
"patternProperties": {
"^[a-z]+$": { "$ref": "file:protocol_details.json" }
},
"$presentation": {
"paths": {
"bacnet": {
"settings.paths": {
"device_instance": { "description": "BACnet Device Instance ID" }
}
},
"modbus": {
"settings.paths": {
"serial_port": { "description": "Serial port for Modbus RTU" },
"baud_rate": { }
}
}
}
}
}
}
//...
}
File 2: protocol_details.json (The Generic Child)
This schema defines a generic settings map. It has no
special $presentation block; it’s designed to be configured by its parent.
{
"title": "Protocol Details",
"type": "object",
"properties": {
"settings": {
"type": "object",
"patternProperties": {
"^[a-z_]+$": {
"type": "string"
}
}
}
}
}
How it works:
network_protocols.bacnet. It sees the settings.paths
object and holds onto it as an “injected configuration”.protocol_details.json to process it.settings property, it sees the injected configuration
waiting for it.device_instance) to build the final properties.Result:
network_protocols.bacnet.settings.device_instance
and network_protocols.modbus.settings.serial_port, each with their own
specific details, all from one generic child schema.A “root schema” is a top-level file that the script starts parsing from. You must explicitly tell the script which files are roots.
This list is managed in docs/guides/presentation_roots.md.
To add a new root schema (e.g., my_new_model.json):
my_new_model.json exists in the schema/ folder.Before:
{
"roots": [
"device_model.json"
]
}
After:
{
"roots": [
"device_model.json",
"my_new_model.json"
]
}
The script will now parse both files when it runs.