Extended properties & templates
Extended properties are Loca.fi's custom-field system: tenant-defined fields that attach to items, places, persons, collections, tasks, and services. An extended property defines a field once (name + data type); a template bundles a set of extended properties into a schema that entities are created against. There is no state machine here — templates and extended properties are plain definitions with no lifecycle states.
The two chains
Everything in this domain is one of two chains: the definition chain (what fields exist and which schema bundles them) and the instance chain (where a record's actual values live).
Definition chain
| Piece | What it is |
|---|---|
| Extended property | An org-scoped field definition: Name (unique per org + TemplateType), optional Description, a DataType, the TemplateType domain it belongs to, and — for SelectList fields — an EpOptions list of options. Created via POST api/extendedproperties/createextendedproperty. |
| Template | An org-scoped, named bundle of extended properties for one TemplateType. Created via POST api/templates/createtemplate; read back as a TemplateDetailDto via gettemplate/{id}. |
| Template membership row | Each extended property in a template carries per-template presentation data: IsRequired, Grouping (a UI sub-heading), and OrderNo (display order). The same extended property can appear in many templates with different settings. |
DataType, TemplateType, and ReferenceTemplateId are immutable after
creation. updateextendedproperty
only accepts Name, Description, and EpOptions. If a field needs a
different type, create a new extended property.
Instance chain
- SKUs and items: a SKU is created against an Item template
(
SkuMaster.ItemTemplateId) — that template governs which extended properties every item of that SKU carries. See SKU lifecycle for the rest of what a SKU carries. - Places, persons, and collections each hang off a shared Entity host
record whose required
TemplateIdpoints at their template. Their values live inEntityExtendedPropertyjunction rows. - Values are stored in
ExtendedPropertyValuerows with one typed column per data-type family (date, string, number, decimal, bool, option selection, and GUID reference columns). On the wire, a value always travels as a string (WriteEntityExtendedPropertyDto:{ ExtendedPropertyId, Value }); the server parses it into the right column per the field'sDataType. Reads returnReadEntityExtendedPropertyDto, which adds the definition metadata (name, data type,IsRequired, options, grouping/order) alongside the value — dates come back asyyyy-MM-ddTHH:mm:ssZ.
The TemplateFor domains
TemplateType (a TemplateFor value, persisted as a string) says which
domain a template or extended property belongs to:
TemplateType | Template-driven? | How values attach |
|---|---|---|
Item | Yes — via the SKU's ItemTemplateId | Item create/update extended-property lists |
Place | Yes — TemplateId on place create | Place create/update |
Person | Yes — TemplateId on person create | Person create/update |
Collection | Yes — TemplateId on collection create; the only domain allowed reference data types | Collection create/update |
Task | No — template-less | Extended properties attach directly to tasks/schedules |
Service | No — template-less | Extended properties attach directly to services/records |
Templates can only be created for Item, Place, Person, and
Collection — createtemplate rejects Task/Service with a validation
error. Task and service extended properties skip the template layer
entirely: you create them with TemplateType: "Task" or "Service" and
supply them directly in the task/service write DTOs.
Data types
DataType is a TemplateDataTypes value (string on the wire). Which types
a field may use depends on its TemplateType:
| Group | Data types | Allowed on |
|---|---|---|
| General | String, Date, DateTime, Number (integer), Decimal, Bool, SelectList | Any TemplateType |
| Item-only (GS1-style) | BatchNumber, ProductionDate, PackagingDate, SellByDate, ExpirationDate, SerialNumber, LotNumber | Item only |
| Reference (advanced) | Item, Place, Person, Collection | Collection only — the field's value is the referenced record's GUID |
| Reserved | AutoId | Reserved — not implemented; no generator exists. Do not use. |
Parsing rules worth knowing (server-side, when a string Value is written):
dates are parsed and stored UTC; an unparseable Bool coerces to false;
reference-type values must be GUIDs. A SelectList selection is stored as
the chosen option's value; the option catalogue itself lives on the
definition as EpOptions ({ UserDefinedId, Value } pairs).
Editing an extended property's EpOptions (adding, renaming, or removing
options) does not touch values already stored on entities or items.
Removing an option leaves existing records pointing at a
no-longer-offered option. This is by design — clean up stored values
yourself if an option is retired.
ReferenceTemplateId — scoping a reference field
A reference-type field (Item/Place/Person/Collection data type on a
Collection template) may optionally set ReferenceTemplateId to constrain
which kind of record it selects: the referenced template's TemplateType
must match the field's DataType (e.g. a Place-typed field pointing at a
Place template). Leaving it null allows any record of that type.
ReferenceTemplateId is only meaningful for these Collection-template
reference fields. On any other TemplateType/DataType combination the
value is stored but ignored — don't set it there.
Template update is a live cascading migration
POST api/templates/updatetemplate (reference)
is not a simple rename. The request carries the full desired set of
extended-property rows, and if the set (or any row's
IsRequired/Grouping/OrderNo) changed, the server migrates every record
using the template — in one transaction:
- The template's membership rows are reconciled: new rows added, omitted rows hard-deleted, kept rows updated.
- Every live entity (place/person/collection) on the template is re-synced: added fields appear with empty values; removed fields cascade-delete their stored values.
- For an Item template, every SKU on the template is re-synced, and then every item of those SKUs — potentially a very large write for big item populations. It runs atomically; any failure reverts the whole migration.
Two practical consequences: an update to a heavily-used Item template may be long-running, and values removed by the cascade are unrecoverable. See the Templates & extended properties guide for how to sequence this safely.
System templates
Loca.fi also auto-generates read-only system templates per organisation
(device reader configs, portal rules, integration agents) on the internal
api/systemplate/... surface. They reuse the same template/extended-property
shapes but are not user-editable — you will encounter them only when
provisioning devices or agents.
Related
- Templates & extended properties guide — the step-by-step setup walkthrough.
- SKU lifecycle — SKUs carry the Item template that governs their items' fields.
- Identifiers & natural keys — GUID vs natural-key conventions used throughout.
- Errors & response conventions — the 400 validation-array convention all these endpoints follow.