Templates & extended properties
Extended properties are Loca.fi's custom fields; templates bundle them into the schema an entity is created against. This guide sits between Reference data setup and SKU onboarding in the setup path: define your custom fields before creating SKUs, because a SKU's Item template is chosen at SKU create and governs every item of that SKU.
Read Extended properties & templates first for the domain model — this guide is the doing, that page is the understanding.
Overview
The flow is always the same four moves:
- Define extended properties —
POST api/extendedproperties/createextendedproperty. - Bundle them into a template —
POST api/templates/createtemplate. - Assign the template — SKUs at SKU create
(
ItemTemplateId), places/persons/collections at their create endpoints (TemplateId). - Read/write values through the owning domain's endpoints — never through the template API.
Template endpoints are gated on the Template module permission,
extended-property writes on ExtendedProperty (the EP list endpoint
accepts any authenticated principal — it feeds device sync).
Prerequisites
- A token with
TemplateandExtendedPropertycreate permission — see Getting started. - The concepts page, especially the
data-type rules: item-only types (
BatchNumber,SerialNumber, expiry dates, …) requireTemplateType: "Item"; reference types (Item/Place/Person/Collection) requireTemplateType: "Collection".
Step-by-step
1. Define an extended property
Pick the DataType and the TemplateType domain the field belongs to:
curl -sk -X POST "http://<host>/api/extendedproperties/createextendedproperty" \
-H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \
-d '{"Name":"Fabric","Description":"Primary fabric composition",
"DataType":"String","TemplateType":"Item"}'
Returns 200 with the detail DTO — capture the Id for step 2.
For a SelectList field, supply the option catalogue as EpOptions
(each option is a { UserDefinedId, Value } pair — UserDefinedId is your
stable key for the option, Value is what users see):
curl -sk -X POST "http://<host>/api/extendedproperties/createextendedproperty" \
-H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \
-d '{"Name":"Condition","DataType":"SelectList","TemplateType":"Item",
"EpOptions":[{"UserDefinedId":"1","Value":"New"},
{"UserDefinedId":"2","Value":"Refurbished"},
{"UserDefinedId":"3","Value":"Damaged"}]}'
DataType, TemplateType, and ReferenceTemplateId cannot be changed
after creation — updateextendedproperty
only accepts Name, Description, and EpOptions. A mistyped field means
a new extended property.
Name must be unique per organisation within a TemplateType; duplicates
return 400.
2. Build a template
Bundle the extended properties for one TemplateType — each row sets
that field's per-template presentation: IsRequired, Grouping (UI
sub-heading, ≤150 chars), and OrderNo (display order):
curl -sk -X POST "http://<host>/api/templates/createtemplate" \
-H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \
-d '{"Name":"Apparel item","TemplateType":"Item",
"TemplateExtendedPropertyList":[
{"ExtendedPropertyId":"<fabricEpId>","IsRequired":true,
"Grouping":"Product details","OrderNo":1},
{"ExtendedPropertyId":"<conditionEpId>","IsRequired":false,
"Grouping":"Product details","OrderNo":2}]}'
Returns 200 with a TemplateDetailDto — the rows come back flattened with
each field's name, data type, and options alongside your
IsRequired/Grouping/OrderNo.
Rules enforced at create:
TemplateTypemust beItem,Place,Person, orCollection—Task/Serviceare rejected (task/service extended properties are template-less and attach directly; see the concepts page).- An unknown
ExtendedPropertyIdis rejected with an invalid-reference validation error, as is the same extended property listed twice. - A non-Collection template may not contain a reference-typed
(
Item/Place/Person/Collectiondata type) extended property. - Template
Nameis unique per organisation.
3. Assign the template
Templates bind at create time of the record they govern:
- SKUs / items — pass the template's id as
ItemTemplateIdonPOST api/skus/createsku. Every item of that SKU carries the template's fields. - Places —
TemplateIdonPOST api/places/createplace. - Persons —
TemplateIdonPOST api/persons/createperson. - Collections —
TemplateIdonPOST api/collections/createcollection.
Use GET api/templates/getfilteredtemplates
(OData-filterable, e.g. %24filter=TemplateType eq 'Item') to find template
ids.
4. Write and read values
Values travel through the owning domain's endpoints, not the template
API. A write is a list of { ExtendedPropertyId, Value } pairs
(WriteEntityExtendedPropertyDto) — Value is always a string, typed
server-side by the field's DataType:
curl -sk -X POST "http://<host>/api/places/updateplace" \
-H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \
-d '{"Id":"<placeId>", ...other place fields...,
"ExtendedProperties":[
{"ExtendedPropertyId":"<epId>","Value":"2026-08-01T00:00:00Z"},
{"ExtendedPropertyId":"<selectEpId>","Value":"Refurbished"}]}'
Reads return ReadEntityExtendedPropertyDto rows: the value plus the
definition metadata (ExtendedPropertyName, ExtendedPropertyDataType,
TemplateExtendedPropertyIsRequired, EpOptions, Grouping, OrderNo) —
enough to render an edit form without a second lookup. Dates come back as
yyyy-MM-ddTHH:mm:ssZ.
String-typing rules to code against: dates are parsed and stored UTC; an
unparseable Bool coerces to false; reference-typed values must be the
referenced record's GUID; a SelectList value is the chosen option's value.
Evolve a template already in use
POST api/templates/updatetemplate
takes the full desired set of extended-property rows — omitted rows are
removed, new rows added, kept rows' IsRequired/Grouping/OrderNo
updated. When the set changes, the server runs a live cascading
migration in a single transaction:
- Additions appear (empty) on every entity/SKU/item using the template.
- Removals cascade-delete the stored values from every record — removed values are unrecoverable.
- For Item templates the cascade re-syncs every SKU on the template and then
every item of those SKUs — may be long-running for large item
populations. Any failure reverts the whole migration (
400with the accumulated errors).
Practical sequencing: fetch the current shape with
gettemplate/{id}, modify the
returned TemplateExtendedPropertyList, and POST the whole set back —
sending only the changed rows would delete everything you omitted. Schedule
big Item-template migrations outside peak hours.
updateextendedproperty can add, rename, or remove EpOptions at any time,
but existing stored selections on entities and items are not migrated —
removing (or renaming) an option leaves records holding a value that is no
longer in the catalogue. Rewrite affected records' values yourself before
retiring an option.
Error handling
All failures follow the platform-wide
error and response conventions —
400 with a JSON array of { ErrorCode, Message, Field }.
| Situation | Response |
|---|---|
Missing Name / DataType / TemplateType on create | 400 naming the field |
TemplateType not a valid domain name — or Task/Service on createtemplate | 400 invalid reference (task/service extended properties are template-less; only the EP create accepts Task/Service) |
| Item-only data type on a non-Item EP; reference data type on a non-Collection EP or template | 400 |
Duplicate EP Name (per org + TemplateType) or template Name (per org) | 400 duplicate resource |
Unknown or duplicate ExtendedPropertyId in a template's row list | 400 |
ReferenceTemplateId pointing at an unknown template, or one whose TemplateType ≠ the field's DataType | 400 (Collection-template reference fields only — elsewhere the value is stored but ignored) |
| Update/delete with an unknown id | 400 NotFound |
| Get template by unknown id | 400 with empty array [] |
| Delete a template still assigned to a SKU or entity | 400 IllegalDeleteAttempt |
| Delete an extended property still in a live template (or, for task/service EPs, referenced by a live task/service) | 400 IllegalDeleteAttempt — stored values alone don't block deletion |
No token / wrong scheme (Bearer) | 401 |
Related
- Extended properties & templates — the domain model behind this guide.
- Reference data setup — previous in the setup path.
- SKU lifecycle — SKU states; the SKU create that consumes your Item template.
- Reference pages: Create Template, Update Template, List Templates, Get Template, Delete Template, Create Extended Property, List Extended Properties, Update Extended Property, Delete Extended Property.