Properties and Metadata
Purpose
The property system allows entities to carry typed, configurable, structured metadata without creating a new table and CRUD flow for every new attribute.
Examples:
Asset.hostname
Asset.ip_address
Asset.serial_number
BACnet.device_instance
Network.vlan
Commissioning.statusDesign Goals
The property system should be:
- typed,
- validated,
- searchable where appropriate,
- organized into groups,
- usable by frontend forms,
- safe for sensitive values,
- easy for developers to extend.
What Belongs In Properties?
Good fits:
- simple technical attributes,
- device metadata,
- network metadata,
- commissioning fields,
- vendor/model/firmware data,
- optional values that vary by asset type,
- custom customer-specific fields.
Poor fits:
- encrypted secret values,
- complex workflows,
- operational sessions,
- audit records,
- external integration state with behavior,
- high-volume time series.
Property Group
A property group defines a UI and logical section.
Example:
Network
├── IP Address
├── Subnet Mask
├── Gateway
└── VLANRecommended fields:
id
key
name
description
display_order
created_at
updated_at
deleted_atProperty Definition
A property definition describes a field.
Recommended fields:
id
property_group_id
key
name
description
value_type
is_required
is_sensitive
is_searchable
validation
default_value
display_order
created_at
updated_at
deleted_atValue Types
Recommended baseline:
text
long_text
number
integer
boolean
date
datetime
json
ip_address
mac_address
url
email
secret_reference
single_select
multi_select
entity_referenceValidation
Validation metadata should be JSON so it can evolve without migrations.
Examples:
{
"minLength": 1,
"maxLength": 255
}{
"min": 0,
"max": 4194303
}{
"pattern": "^[A-Z0-9-]+$"
}{
"options": ["not_started", "in_progress", "blocked", "complete"]
}Application services should convert property definitions into Zod validators where possible.
Entity Property Value
A property value binds an entity to a property definition.
Recommended fields:
id
entity_id
property_definition_id
value jsonb
created_at
updated_at
deleted_atThere should be one active value per entity and property definition.
CREATE UNIQUE INDEX uq_entity_property_values_active
ON entity_property_values(entity_id, property_definition_id)
WHERE deleted_at IS NULL;Sensitive Properties
Sensitive data should not be stored directly in entity_property_values.value.
Preferred pattern:
property_definition.value_type = secret_reference
entity_property_values.value = { "secretEntityId": "..." }The secret itself belongs to the Vault.
This separation ensures:
- directory views can show metadata,
- secret reveal is controlled,
- audit events are reliable,
- encryption and key management remain centralized.
Property Inheritance
Property inheritance is useful but should be introduced deliberately.
Example:
Site connection method applies to assets unless overridden.Recommended strategy:
- Alpha: explicit property values only.
- Later: add inheritance rules for selected property definitions.
Potential fields for future inheritance:
inheritance_mode: none | inherit_from_ancestor | override_allowed
inheritance_stop_types: string[]Do not add generic inheritance until there is a clear product workflow.
Property Templates
Templates associate property groups with entity types or specific templates.
Example:
BACnet Controller Template
├── Device
├── Network
└── BACnetThis gives the frontend enough metadata to render sections consistently.
Backend Responsibilities
The backend must:
- validate property values,
- enforce required properties where relevant,
- enforce root entity scope,
- enforce edit capabilities,
- prevent direct writes to secret values,
- audit changes to important properties.
Frontend Responsibilities
The frontend should:
- render property groups as form sections,
- choose form controls based on value type,
- show validation messages,
- distinguish secret references from ordinary values,
- avoid implying that metadata replaces custom application workflows.
Example: BACnet Controller
Property groups:
Device
Network
BACnet
CommissioningProperty definitions:
Device.manufacturer: text
Device.model: text
Device.serial_number: text
Network.ip_address: ip_address
Network.subnet_mask: ip_address
BACnet.device_instance: integer
BACnet.network_number: integer
Commissioning.status: single_selectExample values:
{
"manufacturer": "Trend",
"model": "IQ4E",
"serial_number": "TRD-001234",
"ip_address": "192.168.10.21",
"device_instance": 400121,
"commissioning_status": "in_progress"
}