Skip to main content

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:

  1. Define extended properties — POST api/extendedproperties/createextendedproperty.
  2. Bundle them into a template — POST api/templates/createtemplate.
  3. Assign the template — SKUs at SKU create (ItemTemplateId), places/persons/collections at their create endpoints (TemplateId).
  4. 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 Template and ExtendedProperty create permission — see Getting started.
  • The concepts page, especially the data-type rules: item-only types (BatchNumber, SerialNumber, expiry dates, …) require TemplateType: "Item"; reference types (Item/Place/Person/Collection) require TemplateType: "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"}]}'
Choose DataType and TemplateType carefully — they are immutable

DataType, TemplateType, and ReferenceTemplateId cannot be changed after creationupdateextendedproperty 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:

  • TemplateType must be Item, Place, Person, or CollectionTask/Service are rejected (task/service extended properties are template-less and attach directly; see the concepts page).
  • An unknown ExtendedPropertyId is 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/Collection data type) extended property.
  • Template Name is unique per organisation.

3. Assign the template

Templates bind at create time of the record they govern:

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 (400 with 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.

Editing SelectList options does not migrate stored selections

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 conventions400 with a JSON array of { ErrorCode, Message, Field }.

SituationResponse
Missing Name / DataType / TemplateType on create400 naming the field
TemplateType not a valid domain name — or Task/Service on createtemplate400 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 template400
Duplicate EP Name (per org + TemplateType) or template Name (per org)400 duplicate resource
Unknown or duplicate ExtendedPropertyId in a template's row list400
ReferenceTemplateId pointing at an unknown template, or one whose TemplateType ≠ the field's DataType400 (Collection-template reference fields only — elsewhere the value is stored but ignored)
Update/delete with an unknown id400 NotFound
Get template by unknown id400 with empty array []
Delete a template still assigned to a SKU or entity400 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